Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

REST: What to return when a request changes state of more than one entity?

Tags:

rest

REST service has two nodes

GET /items returns list of Items:

[
  {
    "id": 34,
    "name": "apple",
    "available_amount" : 10
  },
  {
    "id": 37,
    "name": "banana",
    "available_amount" : 30
  },
  ...
]

POST /orders creates order with requested items (assume we order 6 apples here) and returns the order:

{
  "id": 12337,
  "items": [
    {
      "id": 34,
      "name": "apple",
      "ordered_amount": 6
    }    
  ]
}

How do I inform the client application about the created order which also reduced the available_amount to 4 since 6 apples have been sold.

Do I return the affected resources with the order result, so the client knows what to update?

Like with nodes path:

{
  "id": 12337,
  "items": [
    {
      "item_id": 34,
      "ordered_amount": 6
    }
  ],
  "affected_resources" : [
    "/items/34"
  ]
}

or even the fully changed resource:

{
  "id": 12337,
  "items": [
    {
      "item_id": 34,
      "ordered_amount": 6
    }
  ],
  "affected_resources": {
    "items": [
      {
        "id": 34,
        "name": "apple",
        "available_amount": 4
      }
    ]
  }
}

or something completely different?

What is considered best practice in these cases?

like image 618
TomGrill Games Avatar asked Feb 15 '21 10:02

TomGrill Games


2 Answers

Your request was a POST that creates a single new Order resource.

While it is relevant to the client that things may happen to other resources, that is only true if those resources are within the same bounded context. Inventory is clearly a different bounded context.

At a minimum send only what you can claim is correct at that time, which is the ID of the new order or a representation of the new order. There could be more to the story though...

At 1000 orders per second, you could send invalid data about your inventory.

Now, if informing the client that their order of apples and bananas was indeed an "order of apples and bananas" then you can safely tell your client that; that's a nice feature because you're transferring the state the client sent to you back to the client, and it's the truth. You can even tell the client how many of each they ordered. Still truth. Saying you have N apples and M bananas left... well that might be a lie.

While we are talking about resources here, which are not the same thing as entities in the DDD sense, there's a close enough approximation. Resources are often (but not always) representations of DDD entities (and by extension aggregates). Your API usually is going to operate against a single bounded context, so introducing concepts like inventory is clearly not in the ordering bounded context.

If you want to make inventory a thing of interest to clients, that's a separate API.

A Short Story

Two customers place orders. Your API dutifully takes their orders.

Customer A is a retail customer and just wants their stuff. You inform them you got their order (A book on REST, and a cloud subscription, right?) and the customer sees that and the order tracking link, and starts requesting order status. "Where's my stuff?"

Customer B is a wholesaler and asks for 1000 of those REST books that it wants to have on hand to fulfill when they resell them. You send them their order, which also contains links to the book inventory. Customer B is glad because those books sell quickly, and needs to ask you (via your separate API of course) whether you have 500 more. Your other API checks inventory and says "I have 500 I can give you right now, but that's only good for the next 15 minutes..."

In short, I'd say return anything from the minimum amount of information to indicate the POST was successful (an ID and a 201 response code) to the maximum (everything about the order that was true when the client sent it, plus anything related that is also guaranteed to be true from within the bounded context).

The ideal for APIs are to communicate representations of resources to the client that the client can reasonably act on with success. This includes data about resources and data representing actions the client can take.

While it's a bit beyond your question, HATEOS provides a lot of guidance in this area (blog post).

like image 164
Kit Avatar answered Sep 28 '22 01:09

Kit


There's a couple of answers to this, I'll share how we do this.

Entities are addressable by a URI, so we want a request on entity A to have an effect on entity B, with a different uri.

A POST request on /orders could tell a client that /items is now expired, using an invalidates link:

Link: </items>; rel=invalidates

Our client listens for these links and automatically expels things from it's cache when they pop up. This client also lets you listen for these 'stale' events and automatically refresh.

itemsResource.on('stale' => itemsResource.refresh());

Another way we handle this is via a Websocket. The cases we use websockets, we also emit 'stale' events for specific uris. The benefit of this approach is that if another user adds items to an order (and reduces the inventory), this will also work.

A third option could be use something like HAL. The HTTP response to your POST request could:

  1. Return the full representation of the order
  2. Include a Content-Location header indicating that response body matches the representation.
  3. Then you could use HAL's _embedded feature to send along resource stats of other related resources.

All of this requires a relatively smart client. We've built "Ketting" that handles a lot of these cases, but similar designs could be used for other languages.

like image 34
Evert Avatar answered Sep 27 '22 23:09

Evert