5

Assume following domain model:

An [Order] consists of a number of properties, as well as a list of [OrderLine]s.
For our purpose, an order is fairly crud-like with very little business logic.

Consider the following user story: a user retrieves an order. He then deletes one order line and adds 2 more. Finally, he saves the order.

How would this be implemented in REST? I see a few options but each with certain drawbacks:

  1. Coarse-grained approach:
    The save action will result in a PUT request (PUT /orders/3) containing the entire order including order lines. The drawback is that it's hard to figure out what has changed (i.e. which order lines should be added / updated / deleted?).
    I can see this turning into a mess real quickly when later it turns out there is specific business logic when adding an order line. It basically boils down to replacing one order with another and keeping our fingers crossed that everything is valid, unless we decide to re-implement business rules client-side.

  2. Fine-grained approach:
    The delete order line action will result in a DELETE request (DELETE /orders/3/lines/5). The add order line action will result in a POST request (POST /orders/3/lines). This implies the app now needs to keep track of a lot of changes, and execute the changes in the correct order when the user decides to save.
    Another drawback is that when a new order is created, it would first have to be saved before order lines can be added.

Since rest deals with documents I'm not sure how those would translate into operations executed on a domain model, given that documents are anemic and domain models are not.

  • 2
    Have you considered PATCH method? In combination with e.g. JSON Patch it could solve your problem. – Tomasz Maciejewski Feb 8 '17 at 2:51
  • Rest deals with resources, not documents. In your case Order and OrderLine could be resources. Rest matches perfect with CRUD – Constantin Galbenu Feb 8 '17 at 6:49
  • If you want something like a transaction, that is, an order not being in an invalid intermediate state between a DELETE and a POST, then PATCH is a way to go. – 9000 Feb 8 '17 at 15:52
4

Since rest deals with documents I'm not sure how those would translate into operations executed on a domain model, given that documents are anemic and domain models are not.

The main thing to keep in mind is the point of a web api is to adapt your domain for the web; which is to say, to provide the illusion that your resources are really just entries in a document store.

In a sense, a consumer should be able to drive your API using a web aware document editor.

Coarse grained approach

The save action will result in a PUT request (PUT /orders/3) containing the entire order including order lines. The drawback is that it's hard to figure out what has changed (i.e. which order lines should be added / updated / deleted?).

This is a perfectly reasonable approach to take - you tell the API how you want the resource to look, and the API has to figure out how to get there.

Since shopping carts don't typically call for collaboration among multiple editors, you might get by building a new data model for the resource from the snapshot you have been given, then overwriting the existing model.

If you, for whatever reason, instead need to discover finer grain command messages from the diff; yeah, that can be awkward. In a case like this one, it's not too bad -- you can detect change easily enough, and any valid change is likely drawn from a small vocabulary.

It's also worth noting that you don't need to support PUT on any arbitrary representation of the resource, but can instead pick and choose a representation that makes sense (and, hopefully, one where it is easy to identify the correct changes).

Fine grained approach

The delete order line action will result in a DELETE request (DELETE /orders/3/lines/5).

Any URI you want to use is fine as far as REST is concerned; but conceptually it might be better to think in terms of removing the entity by id, rather than by position in the list.

The add order line action will result in a POST request

Could also be done as a PUT.

This implies the app now needs to keep track of a lot of changes, and execute the changes in the correct order when the user decides to save.

Yes; I believe you run into a similar situation with an occasionally connected client. Command messages get queued up until the target system becomes available.

Another approach to consider would be to manipulate the history of the resource, rather than the resource itself. Effectively, the history becomes an append only collection of changes.

I'm just wondering how I can avoid duplicating business rules on the client. For example, if an order line cannot be deleted under certain circumstances, nothing prevents the client from doing so.

There are a couple of answers here, based on the idea that you signal that information in the representations.

One answer is that you simply annotate that the order line should not be deleted. The business rules that decide whether an order line can be deleted live on the server, the client just sees that the flag has been set.

You can communicate that information out of band to the clients.

In the fine grained approach, the usual answer is that you use hypermedia controls. Rather than clients sending a message to a pre-determined endpoint, the client is expected to look in the representation and "follow a link". So you can communicate the fact that an order item can't be deleted by removing the delete item link from the representation.

In a course grained approach, I suppose you could try distinguishing the modifiable parts of the resource from the rest, and providing editable representations of only the parts that the client is allowed to edit. There's liable to be some confusion there.

  • "This is a perfectly reasonable approach to take - you tell the API how you want the resource to look, and the API has to figure out how to get there." - I'm just wondering how I can avoid duplicating business rules on the client. For example, if an order line cannot be deleted under certain circumstances, nothing prevents the client from doing so. Only when the resource is saved can an error be returned. – Bart Feb 8 '17 at 11:56
1

Consider the following user story: a user retrieves an order. He then deletes one order line and adds 2 more. Finally, he saves the order.

Fundamentally, by having a rich client-side edit & save capability, you are talking about a coarse-grained approach, in which the client can make an arbitrary number of adjustments and saves the final result. The client is unlikely to expect that their every client-side edit step is played to the server on save; more likely they expect the final result pushed to the server.

(For example, if they make insertions and deletions of the same items before saving, the won't expect the insertion to be checked by business rules, since it was deleted before saved.)

Many shopping carts do not have a complex client-side edit then save function, so for them, they naturally work on a finer granularity. Edits of the cart only allow changing counts of line items, for example. Inserts of new line items don't happen as edits & save of the cart but as a different operation, e.g. from a product (page).

Your REST interface needs to support the client behavior that is wanted, whether coarse, fine (or both with several ui's).

1

If all you have is a hammer, everything looks like a nail.

Solution to your problem may be to use the "commands" (smilar as in the CQRS and CQS pattern).

POST /api/order/188423
{command: "AddOrderLine", "item_id": 123}

After POSTing this command the API would return the updated order resource. This approach is easier to understand and more testable. It also enables you to execute more complex logic affecting many entities in an atomic way. You can also use async commands for more time-consuming calculations.

There is an article written by Ali Kheyrollahi that treats about mentioned patterns with REST.

See also the RESTful way to send commands.

0

If there are no business rules now around order line removal then the coarse grained method is fine. The order lines are just value objects. You mention "when later", when that moment comes change your model and possibly your REST interface.

Your application layer could translate your document into operations against the order.orderLines collection, while it does so business rules can be applied.

Your point 2 confuses me a little. REST is supposed to be stateless so you should not be doing 'keeping track of changes for when saving'. Keep the transient item on your client, send the completed result.

Your REST interface is part of an application layer. It does not need to mimic your domain semantics.

The fine grained option perhaps makes more sense in a situation where you want to remove an item from an already submitted order. You would not be sending whole order documents in this context.

  • In DDD I believe that Rest is a part of Presentation layer – Constantin Galbenu Feb 8 '17 at 7:44
  • "Your application layer could translate your document into operations against the order.orderLines collection" - Conceptually, I'm not sure how I would figure out exactly which operations should be made on the domain object purely based on the received document, i.e. which lines should be added, removed, updated? – Bart Feb 8 '17 at 11:40
  • @Bart There are many models for this. If they are just value objects throw what you have away and just add the new orderlines. If this is not acceptable, they should probably become entities, meaning they will have an ID. You can figure out what is what using the IDs (you can detect deletes, additions, re-ordering etc). – Joppe Feb 8 '17 at 13:00
  • 1
    @Constantin GALBENU you can consume REST API from another (web) service also, their is no presentation layer involved there. – Joppe Feb 8 '17 at 13:02
  • @Joppe we must agree on the selected architecture before we argue about this. In Layered arch rest is at presentation layer. In Union arch rest is a port etc. – Constantin Galbenu Feb 8 '17 at 14:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.