RFC: Neos API - next steps

Hey @dimaip .

I try to answer you point by point by my jsonapi.org point of view.
That’s nothing we discussed in our hangout, but to me that’s the way to go when jsonapi.org would be the format to use. Just take those as “my personal idea of what a natural implementation would look like”.

And after having spent several hours deeply into jsonapi.org im kind of a fanboy now.

That’s exaclty what I tried to do. The jsonapi.org uses a pretty RESTy approach. But that’s not a problem at all when it comes commands. More later.

For Nodes I dropped every writing part and only implemented the reading parts. So reading goes in a RESTy way of the known domain model. If you don’t want to write but only read (e.g. if you skip server side fluid rendering and make a client-only application), the read model looks pretty RESTy and follows the current node tree by 100%.

I tried to think of Commands as “Domain objects that only represent input arguments”. So I actually created a domain model class CreateNodeCommand that has a property “parentNode” and a property “type”. From the client perspective, the command execution looks like a RESTy “create new CreateNodeCommand object”. So it’s done by posting a command JSON to the CommandController::createAction().

This makes both channels, the read channel as well as the write channel, use the same format (jsonapi.org) although the domain content being transported is different.

Same goes for events. An event is, after all, just a domain model that can be exposed in a RESTy way to the client.

Of couse this concept is not bound to jsonapi.org. It’s not even bound to JSON. Instead, I’d suggest to go this way with whatever format we decide on. Treating commands as “command entities” kind of boils lot of things down to the a well known level.

To me, the format look like jsonapi.org with different wording.
I use your post in the other thread as an example.

There’s no “changes” and no “feedback” in jsonapi.org since that is not only something that changed or is a response. Instead, jsonapi.org just always calls that level “data”

The “type” exists in jsonapi.org in exactly the same way. It is just for being able to distinguish different object types, the actual value doesn’t matter to jsonapi.org. But as you can see by comparing your request and response, the “type” property differs, so there’s no need to make the top level property (changes and feedback) different as well.

The “subject” is called “id” in jsonapi.org. The content doesn’t matter to jsonapi.org. Uuids are prefered, but using node paths go very well, too.

What @wbehncke called “payload” is called “attributes” in jsonapi.org in case its trivial information or “relationships” in case those are aggregate roots.

But there is one important difference: Request and response by definition of jsonapi.org “target the same resource”. You’re not allowed to “post a Create object and receive an Info object in return”. If you’re posting a Create object, you always return either that very same Create object back or “204 No Content”.

In case CQRS with a distinct events channel is not possible, a way to circumvent the “input equals output” rule would be to make the info response a property of the command object.

Just give the Command object a property $info. After a command is executed, its $info array is to be filled with the response information.

Now you can POST the Command object not to /api/commands but to /api/commands?include=info. That would internally reach the very same CommandController::createAction() and in addition, the jsonapi.org view is automatically configured to include the info relation of the Command object.

And here you are: You get the exact same command as a response as you pushed in, enhanced by every information the creation process collected.

But to be honet, I wouldn’t go that way.

I strongly suggest to separate read channel, write channel and event channel.
The read channel is our RESTy way of GETing nodes from /api/nodes.
The write channel is our RESTy way of POSTing commands to /api/commands.
The event channel should be a RESTy way of GETing events from /api/events by polling.

The polling interval can be managed smart. I’m thinking of once every 30 seconds in general, 5 times with a reduced delay to 5 seconds right after a write command received its 200, 201, 204 or 400 status code. Call it"polling schedule influenced by request responses".

An advanced implementation could drop the polling and use a web socket for that. Since every tiny part of information of a jsonapi.org object is completely covered by the JSON payload and no URL is required to e.g. guess the type or the ID of a thing, using web sockets as a response channel is pretty easy in terms of the protocol.

The /api/events list can be either stateful or stateless. Stateful would leave it up to the user session which events should be returned to the client and which are already transported. Stateless would leave it up to the client asking only for events starting by a distinct sequence number the client provides.

I thought about that, too. But I’m not completely sure if that’s the power we want an API consumer to have.

Have a look at this one:

Sine it’s an Eel query string being evaluated anyway, making it a full input argument is an easy thing. But that would require really rock solid content security. By no means a consumer should be able to break out of its privileges just by traversing nodes.

The “nested data response” is up to the client if jsonapi.org is the selected format.

Just issue this query:

GET /api/nodes?filter[eel]=%24%7Bq(node).parent().find(%22%5Binstanceof%20TYPO3.Neos%3AShortcut%5D%22).get()%7D&include=childNodes,parents,workspace.user.party.electronicAddress
Accept: application/vnd.api+json

This request would

  • return every shortcut in a raw list as requested by the eel query,
  • have every childNode of those shortcuts included,
  • the rootline (“parents”) for the shortcut,
  • the workspace for the shortcut,
  • the corresponding user to the workspace,
  • the corresponding party to the user and
  • the corresponding electronicAddress to the party.

Once again this is a huge thing for content security, but it’s the actual feature set of basic jsonapi.org include arguments in conjunction with the Eel based search.

I find this an amazingly powerfull feature. Only we need a client that can put this to good use.

Regards,
Stephan.