How to implement HTTP's PUT that works with child collections when using DDD's rich domain models?
With great difficulty.
PUT, like DELETE and PATCH, have remote authoring semantics. "Make the resource look like this".
The basic idea being that I can take any HTTP aware editor that understands the media type, and use it to load a new representation, make changes to it, then store the changes -- without needing to know anything about how the origin server stores that information.
Thus, the burden is on the server to figure out how to convert the new representation into commands to send to its own backing storage.
One possible answer is to put more of the burden on your media type. Resources are allowed to have more than one representation, and you aren't required to support PUT
for all of them. 415 Unsupported Media Type
is there for you in the case where you need to reject a PUT
that you can't convert into messages your domain model will understand.
That flow might look something like
GET /foo
Content-Type: application/json
And the consumer looking at this representation sees that they want to edit the resource. So it asks for the editable representation
GET /foo
Content-Type: application/vnd.hacky-workarounds.edit+json
PUT /foo
Content-Type: application/vnd.hacky-workarounds.edit+json
You might imagine a representation analogous to application/json-patch+json, with operations that match the command message that your domain model understands, and a query operations that describe the initial state (for instance, the hash of the current tree).
Depending on your schema, the client might be able to convert the original DTO to the editable representation on its own, without needing to perform a GET. HTTP is stateless, so the subsequent PUT
should behave the same way regardless.
Another approach to consider is that the domain model has two responsibilities: making sure that state is internally consistent, and making sure that the transitions are valid. In other words, the origin server doesn't necessarily need to apply the business rules itself, but instead just validate that the rules have been applied correctly.
Thus, you check that the DTO is internally consistent, and you check that the transition from old state to new state is legal, and if both of those hold you just store the result without worrying about repeating the work yourself.
But the real answer is the one you hate - diff the two representations, reverse engineer the commands required to transform one into the other, and then apply those commands via the domain model.
PUT can be used to create resource, in addition to editing them (provided that you are comfortable with the notion that a client gets some control of the URI). So you could PUT a representation of a command message to some new resource. Likely you would want to use the If-None-Match header to ensure that particular URI is not already occupied. So each command would get a unique URI, selected by the client. What you don't get in that case is cache invalidation of the DTO (which has it's own URI already)
Allowing the client to select a URI for the the command message resource is analogous to allowing the client to select its own correlation identifiers.