Entity Versioning and Conflict Management
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