21

The recommended way to handle optimistic locking in a RESTful interface seems to be by returning an ETag from the GET, and supplying an If-Match on the PUT, ie:

GET /items/1  --> gives client an ETag for a single item
PUT /items/1  <-- client gives it back as If-Match for checking

How do I use this scheme with multiple items, for example if I want to batch GETting multiple items from a single URI:

GET /items    --> How do I return multiple ETags for multiple items here? 

Alternatively, if ETags/If-Match don't cope with this situation, what's a recommended approach? Or should I just roll my own?

stusmith
  • 14,003
  • 7
  • 56
  • 89
  • How are you retreiving multiple items with a single URL in the first place? – Remy Lebeau Mar 24 '12 at 02:21
  • See second example URI... `GET /items` would return me a list of items. – stusmith Mar 24 '12 at 15:18
  • What kind of list? Can you show the actual data? – Remy Lebeau Mar 24 '12 at 17:12
  • 1
    This is all still in the planning stages. My issue is; the ETag information is returned in the HTTP header, so how do I return appropriate information for multiple items? – stusmith Mar 26 '12 at 07:30
  • 1
    You wouldn't be able to return multiple etags in the header. Depending on the format of the body data (xml, etc), you might be able to include the etags as attributes/nodes of the items themselves. That's why I asked what the format of the list is. – Remy Lebeau Mar 26 '12 at 15:16

2 Answers2

26

tl;dr: it's OK to assign etags to a resource that contains a "collection" but it is up to the app to manage the identification/change of the items in that collection when determining a valid etag.

A resource in the REST architectural style does not imply it is a single item or a collection. It simply represents the state of the application at a given point in a workflow. A resource will contain descriptive data and hopefully, links to possible next steps at that point in the workflow. The workflow could be as simple as get an order status or complex like ordering items on an eCommerce site complete with the ability to cancel the order.

An etag is a mechanism for determining whether a particular state (resource) is up-to-date or not. Assigning an etag to a resource that returns a collection of data means that your application is identifying that state of the collection items at that point and if anything in those items changes, the prior etag value would no longer be valid. It's up to your application to decide when collection items change and how to identify the individual items. In other words, etags apply to resources but not individual components of a given resource.

Sixto Saez
  • 12,610
  • 5
  • 43
  • 51
  • That's a really nice answer, thankyou. I guess I was so focused on the underlying database tables I didn't think to apply the resource-style thinking to a collection resource. – stusmith Mar 27 '12 at 07:25
  • Good answer! In this case, I think the question is how to compute the Etag? One candidate that fits well with single item resources is the version (optimistic locking). However, when a collection is returned, a version can't be used since items composing the collection may have different versions. What to use in such case? – manash Feb 16 '15 at 09:57
  • 3
    Another approach I found is using hashes of the returned content. In this case, the hash would be computed on the collection and therefore is a candidate for the ETag header. However, how can this ETag be used for optimistic locking? For example, the client may decide to update one of the items in the collection but he doesn't have the hash of this particular item. – manash Feb 16 '15 at 10:55
  • 1
    Not sure if this is the correct, but i would revert to having the hash a part of each entity in the collection. I cant think of any other way personally. Or make the client pull the single entity they want to update when ready – gdp Jul 17 '15 at 11:33
  • 2
    Maybe sometimes you have to be a bit chattier with the API. If you are going to perform an update against an item in the collection, perform a head request against its resource URI, and use that Etag to perform the optimistic locking – The Prophet Mar 14 '17 at 23:15
  • 1
    @MatthewConcreteBlackmon: performing an additional HEAD request might cause race condition. You firstly get state in version X, but following HEAD request might return version X+1. – krlm Apr 07 '17 at 08:24
  • @Sixto Saez - I get the part where you say a given resource is given an etag and that etag only says that the given resource up to date, but how do you handle a representation made by the client machine. For example, I return a list of 100 products (this list has a single etag in your view of REST), the user selects 80 of them and hits 'Delete'. How do I transmit the etags to the server so the server can make sure that optimistic concurrency isn't violated? – Quark Soup Aug 05 '17 at 20:29
  • 1
    An eTag isn't used that way. The eTag header values are only ever generated on the server-side. For the precise definition, please see the [section 14.19 of](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) the HTTP standard.The term REST in common usage is taken as a synonym for HTTP but they are very d – Sixto Saez Aug 07 '17 at 13:54
  • 1
    ... are very different concepts. Unfortunately, the REST === HTTP naming war was lost a long time ago. It would probably be helpful to understand the differences though, especially since the concept of database concurrency does not apply to either term. – Sixto Saez Aug 07 '17 at 14:05
0

In the case of aggregates the version of the aggregate must be the version of the last updated part. With event sourcing this is plain simple, because you can use the event id for versioning.

As of your item collection, the etag of the collection resource must be the etag of the last updated item resource. For each item you must describe the etag in their hyperlinks or if all the hyperlinks have the same version in the same resource, then you can describe the version in their containing resource.

So for example if item 4 was updated latest, then GET /items should return something like:

{
    "type": "/doc/ItemSet",
    "version": "3q2teg3234",
    "label": "The Collection of Expensive Items",
    "size": 2,
    "items": [
        {
            "type": "/doc/Item",
            "version": "f233425wfsw",
            "id": "1",
            "label": "Bugatti Veyron",
            "links": [
                {
                    "label": "Read item",
                    "type": "/doc/Item/methods/read"
                },
                {
                    "label": "Update item",
                    "type": "/doc/Item/methods/update"
                }
            ]
        },
        {
            "type": "/doc/Item",
            "version": "3q2teg3234",
            "label": "Porsche 911",
            "id": "4",
            "links": [
                {
                    "label": "Read item",
                    "type": "/doc/Item/methods/read"
                },
                {
                    "label": "Update item",
                    "type": "/doc/Item/methods/update"
                }
            ]
        },
    ],
    "links": [
        {
            "label": "List items",
            "type": "/doc/ItemSet/methods/list"
        }
    ]
}

Ofc. you can send the hyperlink descriptions like /doc/Item/methods/update in the same document too, but it is better to have something reusable, so for example:

{
    "type": "/doc/HyperLinkTemplate",
    "method": "PUT",
    "uri": "/api/items/{id}",
    "headers": {
        "ETag": "{version}"
    },
    "body": {
        "label": "{label}"
    },
    "parameters": {
        "version": {
            "type": "/doc/ResourceVersion",
            "value": {
                "type": "doc/Query",
                "query": "{context/containingResource/version}"
            },
            "writeable": false
        },
        "id": {
            "type": "/doc/Item/id",
            "value": {
                "type": "doc/Query",
                "query": "{context/containingResource/id}"
            },
            "writeable": false
        },
        "label": {
            "type": "/doc/Item/label",
            "value": {
                "type": "doc/Query",
                "query": "{context/containingResource/label}"
            },
            "writeable": true
        }
    }
}

It is up to your client how it uses the hyperlink. It is even possible to generate a HTML form from it, though REST is mostly for M2M communication.

inf3rno
  • 24,976
  • 11
  • 115
  • 197