Reputation: 822
I'm designing my first REST-API and am struggling to design the API for a Cart
aggregate that has nested objects:
Cart 1->n SubCart 1->n CartItem.
The Cart
aggregate is saved as a whole. Each time an item is placed in the Cart various discounts have to be calculated that depend on the other CartItem
s even from other SubCart
s. Therefore the client app needs to receive a full Cart aggregate with all nested objects.
Evens says in his DDD book that "... The root is the only member of the AGGREGATE that outside objects are allowed to hold references to...".
I understand the general idea, but it is not clear what reference means. Some articles speak about in memory object references, others speak about id references, too. If the user i.e. wants to increase the quantity of an CartItem
the UI must have a way to identify the item in the cart. It needs to have some ID/reference. Otherwise how shall the Cart
know what item's quantity should be increased. What did Evans mean with reference? The way it is worded is very confusing.
Since all commands go through the Cart
aggregate in the backend I wonder if this also should be applied to the REST API. Since I lack the experience I don't know what issues or side effects one or the other solution will have (i.e. caching, security)? Why would one prefer one or the other?
For example for updating the quantity of a CartItem
I see the following options:
PATCH carts/{id}/subcarts/{subcart_id}/cartitems/{cartitem_id}
.PATCH carts/{id}/{subcart_id}/{cartitem_id}
.PATCH /carts/{id}?subcart={subcart_id}&cartitem={cartitem_id}
.PATCH carts/{id}
having subcart_id
and cartitem_id
in the body.I saw that GIT API used the shorter form of option 2 in some cases. When should one chose option 1 over option 2?
For options 3 and 4 it would be natural to return the new Cart
object with possible discounts as a result of the PATCH
.
With option 1 and 2 it seems not RESTful to return a Cart
object after a PATCH
of a CartItem
. The return could be a 204 and the client must then send a GET
on the Cart
again, resulting in two calls.
Thanks for any help and insights.
Upvotes: 1
Views: 3087
Reputation: 57277
Even the idea behind it is clear to me, it is not clear what reference means. Some articles speak about in memory object references, others also speak about id references. If the user i.e. wants to increase the quantity of an item the UI must have an id/reference to that item. What did Evans mean with reference?
Pointer.
class A {
B b
}
In this example, A "holds a reference to B". According to Evan's guidelines, if A and B are both domain entities, then either this instance of B is a member of the same AGGREGATE as this instance of A or B is itself the root entity of its AGGREGATE.
How to design REST API for aggregate with nested objects?
A REST API is a collection of resources, where resources can be understood to be a generalization of "web pages". Clients send messages to manipulate resources, and useful business activity is a side effect of manipulate the resources. See Webber, 2011.
In other words, the client sends a PATCH/POST/PUT message to the HTTP server, and the server in turn invokes some command(s) on the corresponding aggregate root entity.
PATCH /carts/{id}/subcarts/{subcart_id}/cartitems/{cartitem_id}
PATCH /carts/{id}/{subcart_id}/{cartitem_id}
PATCH /carts/{id}?subcart={subcart_id}&cartitem={cartitem_id}
PATCH /carts/{id}
All of these are fine, in the sense that you can have as many different resources as you like that change the same aggregate root, and also in the sense that you can use any spelling conventions you like for your resource identifiers. The only real constraint here is that the identifiers should be RFC 3986 compliant.
Note: keeping all of the client's locally cached copies of different resources synchronized after changes can be tricky; so if you aren't sure you know what you are doing yet, then my suggestion would be to have a single resource per aggregate.
It is okay to use POST, and you will probably find that sending representations of domain model commands to the server with POST makes the implementation easier than trying to compute a command from a patch document. Remember, the web was catastrophically successful using HTML forms and POST.
Conseptionally I would favor a single resource for Cart, but how shall the client send a command that updates a sub-resource?
Careful - "sub-resource" isn't really a thing in a REST context. We have resources, like
https://datatracker.ietf.org/doc/html/rfc3986
and we have secondary resources, like
https://datatracker.ietf.org/doc/html/rfc3986#section-3.5
Neither of those map particularly well to domain entities inside of an aggregate root. The resource model is a facade in front of the domain model.
In a REST API, the client doesn't interact with the domain model directly. Instead, the client addresses messages to a resource, and the resource interacts with the domain model (via the aggregate root).
So if you want to show some information to a domain entity buried somewhere in the aggregate "below" the aggregate root, then you send that information to some resource in your resource model, and the message handler in your resource implementation shares that information with the aggregate root, and then the aggregate root shares that information with the other domain entities within the aggregate.
The client needs a full cart to display all changes and I wondered if it is OK to return a cart object from a CartItem resource to avoid two round-trips each time. Would this cause any issues that I'm not aware of?
That will probably be fine if you include the correct metadata in the response.
PATCH /carts/1/2/3
...
200 OK
Content-Location: /carts/1
...
It's not really all that different using a POST request that targets a "collection" resource to create a new resource on the server.
That said, if you are sending requests to /B
to change the representation of /A
, then you are swimming against the current of the design of the HTTP application, and may want to reconsider.
Upvotes: 2
Reputation: 444
My suggestion is to keep it as simple and understandable as you can.
First of all, REST guidelines are conventions, not rules written in stone. Also, you say you are afraid not to be RESTful. I'm almost sure you will not be so anyway :-). For example, I'm almost sure you are not going to implement HATEOAS at all, and without it you are not creating a RESTful system (like the vast majority of the so-called REST APIs you find around, after all :-))
That said, you should think in terms of resources and CRUD operations you want to perform on them. Since discounts depend on the presence of other items in subcarts, my suggestion is to consider the cart as your resource, including its subcarts and items. This simplifies your work and the overall understanding of the system.
Make your decision based on performance and clarity issues.
Upvotes: 0