Resource version control using ETag

Controlling versions using HTTP headers

André Rodrigues
Wellhub Tech Team (formerly Gympass)
5 min readOct 20, 2022

--

Overview

You’ll find that almost every company has a system that is referred to as “The Source of Truth”. In most cases, it does not mean that this service owns and manages the data by itself, but instead it is a place where other systems reflect their changes.

Within the context of distributed systems, this scenario will imply that systems that depend on and interact with The Source of Truth will have to synchronize and make sure they are all on the same page.

Writing a Wiki Document

Let’s consider a scenario (Fig.1) where a user is writing a Wiki document. When the user saves the document it is saved in Wiki’s service (“The Source of Truth”).

Fig. 1 — User saving Wiki document

Now let’s consider another scenario (Fig.2) where two users are writing and saving the same Wiki file. Which user is going to have his document changes saved?

Fig. 2 — Two Users saving the same Wiki document

In this scenario, we can identify that both users are doing the same operation on the same resource resulting in a mid-air collision if they submit the changes at the same time.

Versioning

To make sure a user does not overwrite another user’s changes accidentally, an option is to save versions of the Wiki document.

Let’s say that the Wiki service now saves the version of a document, and to simplify let’s consider that this version is incremental. Then, during the communication between the client and the server, we need to identify the resource version we are working on. We can do so by taking advantage of some HTTP headers.

HTTP headers allow the client and the server to pass supplementary information within an HTTP request or response. A very common header is the ContentType which is used to indicate the original media type of the resource, for example: ContentType: application/json.

When dealing with resource versioning, some useful HTTP headers are: Etag, If-Match, and If-None-Match.

  • The ETag (or entity tag) response header is an identifier for a specific version of a resource. It helps to prevent simultaneous updates of a resource from overwriting each other.
  • The If-Match request header makes a request conditional. If the sent version in theIf-Match header does not match the resource’s version on the server (Etag), a 412 Precondition Failed response is returned.
  • The If-None-Match request header makes the request conditional and can be used to check if the cached versions are still up-to-date.
Fig. 3 — Versions Match

In Fig.3, when editing the wiki document, the current version is set into an If-Match header in the request:

If-Match: "1"

And if the request is successful, the response returns the ETag header with the new document’s generated version.

ETag: "2"

Then, if another user attempts to save its changes in the same document, Fig.4, it will set the If-Match header in the request to:

If-Match: "1"
Fig. 4- Versions Mismatch

And versions will not match because the latest version of this document is now 2, resulting in a 412 Precondition Failed response.

Caching

To avoid this mismatch and take advantage of the cached version, the current version that this user thinks is the latest can be set into an If-None-Match header in the request, Fig.5.

Fig. 5 — Checking cached version validity before submitting changes
If-None-Match: "2"

In this scenario, the Wiki service will compare the latest version with the one given by the user (in If-None-Match header), and if it matches it will result in a 304 Not Modified, which tells the user that his version has not been changed by anyone else and is still fresh.

However, if the versions mismatch, the response is 412 Precondition Failed and the user has to GET the latest document version and update its document before submitting a change.

Microservices Ecosystem

In the previous examples, two users were writing in the same wiki document, and, that’s not much different than a microservice distributed environment where services have to interact with each other and also contain a “The Source of Truth” service.

Let’s consider a company that has a service responsible for managing client contracts, we’re calling it Clients API.

Commonly, there are some flows to create a new client contract, and depending on the client it can be created via:

  • Back office: details are filled manually, validated, and submitted to the Clients API.
  • Self-Checkout: automatic flow to purchase and operate the contract. In its last step, the data is submitted to the Clients API. (e.g.: flow developed by the company).
Fig. 6 — Back office and Self-Checkout interaction with Clients API

Thus, regardless of the client contract flow, there must be synchronization with the Clients API which is The Source of Truth” service, Fig.6.

To validate any operation on a Client contract, its version is sent using the If-Match and ETag headers.

We may also use If-None-Match to validate if the cached version is still up-to-date and update the respective User Interface accordingly using a polling strategy for example.

The versioning control behavior is similar to the Wiki document example.

Finally, we can guarantee that there are no collisions or unexpected overwrites on a Client contract.

References

--

--