Entity Versioning and Conflict Management

How to manage versioned entities and prevent conflicts in your API.

Entity versioning and conflict management are essential for ensuring data integrity. The headers described in RFC 7232 provide us with the tools we need to communicate the version of an entity, and decide whether it can be modified. Our intent here is to create a client/server contract, providing an opt-in method to manage staleness during API calls. Data can only be returned to the client if it is demonstrably fresh, while modifications should only be allowed if the client can demonstrate awareness of the version they are modifying.

Headers

There are two headers by which an entity’s version or state is communicated by a server to a client: ETag and Last-Modified. You must return both in simple GET requests for a specific resource, though it is not specific on how they should be handled in a query, aggregation, or graph query.

ETag

An ETag is an opaque string that can weakly or strongly assert the content of the entity. An example of a weak ETag is one derived from a second-precision modification date, as multiple changes could have occurred within that time window. An example of a strong ETag is a SHA-256 of the entity body as persisted in the database, as it is the content itself that communicates freshness.

// Weak tag
ETag: W/"123456"

// Strong tag
ETag: "da39a3ee5e6b4b0d3255bfef95601890afd80709"

For the purpose of this contract, only strong ETag values are permitted, even though they may be difficult to generate. All ETags are represented as hexadecimal strings such as those generated by a SHA-256, and must be independently reproducible from the entity.

Last-Modified

Each resource entity should also return the Last-Modified header as per RFC 7231 Section 7.1.1.1, which is a timestamp of the last modification of the entity.

Last-Modified: Sun, 06 Nov 1994 08:49:37 GMT

Simple Responses

If the entity is loaded by itself via a simple GET request, the ETag and Last-Modified headers should both be present as per the RFC.

HTTP 1.1/200 Ok
Last-Modified: Sat, 29 Oct 1994 19:43:31 GMT
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Content-Type: application/json

{
  ...
}

Requests using ETag

To manage staleness and conflict when accessing a single resource, the client uses an If-None-Match or If-Match header to communicate to the service the conditional parameters under which it wants to operate. This contract is required only on single resources, as calculating ETags for large collections is prohibitively expensive.

If-None-Match

Here, the client wishes to read a resource, but only if there is a new version.

HTTP GET /v1/resource/123456
If-None-Match: ABCDEF

And the server returns that the content has not been modified.

HTTP 1.1/304 Not Modified

If-Match

Here, the client wishes to modify a resource, but only if it has not changed since the client loaded it.

HTTP POST /v1/resource/123456
If-Match: ABCDEF

And the server indicates that the resource has changed.

HTTP 1.1/412 Precondition Failed

Requests using Last-Modified

The other way of managing staleness is to use the If-Modified-Since or If-Unmodified-Since headers. The largest criticism of this method, is that it is dependent on the server’s clock being accurate, and that the date was stored with an appropriate precision. Since it is likely that multiple chances can happen in the same second window, this method is less reliable than ETags.

If-Modified-Since

Here, a client wishes to read a resource, but only if there is a new version.

HTTP GET /v1/resource/123456
If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT

And the server returns that the content has not been modified

HTTP 1.1/304 Not Modified

If-Unmodified-Since

Here, the client wishes to modify a resource, but only if it has not changed since the client loaded it.

HTTP POST /v1/resource/123456
If-Unmodified-Since: Sat, 29 Oct 1994 19:43:31 GMT

Server indicates that the resource has changed

HTTP 1.1/412 Precondition Failed

Collections

Both ETag and Last-Modified headers must be included on collection responses, and they should be derived from the returned page of results, rather than the entire result set. As an example, let us presume that a client is querying a collection of resources, but only if it has been modified:

HTTP POST /v1/resource/query
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT

{
  ...
  results: [
    ...
  ],
  ...
}

And the server indicates that the list is still fresh.

HTTP 1.1/304 Not Modified