In a previous article, we described how to choose a HTTP method when implementing operations that affect resources. Today, we are going to explain how to implement services that manage concurrent operations on a resource and how clients should use such services.
The Problem
Sometimes, Apps must handle concurrent updates on a resource. Just imagine an application that acts like a Wiki, where users read, write, and edit articles. Now imagine that user Bob and user Mary modify the same article at the very same time, but Bob updates the article faster than Mary and submits his changes. When Mary submits her changes, she overwrites Bob’s. That’s bad news for Bob because his changes are lost. Usually, there are two ways to manage concurrent updates on resources:
- Pessimistic concurrency control – This implies that the service locks the resource so that a client cannot updated it. While the resource is locked, no other client can modify it.
- Optimistic concurrency control – This implies that a client first obtains a token for the update operation. Once the token is received, it allows the client to perform the update. However, the changes will only apply if the token is still valid.
Because HTTP is stateless, it is possible to use only optimistic concurrency control.
Optimistic Concurrency Control
First of all, not all resources are suited for concurrency control. For example, you might not care if both you and somebody else add photos to the same album at the same time (actually, that might be considered a feature). Yet, you might care if you and a friend edit a file at the same time.
In such a case, you can implement a service that controls the updates on files using optimistic concurrency control. This means that the service always expects to receive If-Unmodified-Since and If-Match HTTP headers. These headers are also called conditional headers, because they condition the service to respond according to their values. When sent by the client, they take the values of the Last-Modified, respectively ETag headers.
Last-Modified is a header that holds the date the resource was last modified. The ETag header is a string representation of the resource at the time the request was made. There is no explicit rule on how to generate the Etag. For example, you can generate it by applying a crypto hash like MD5 on the representation of the resource. Services can be implemented to return these headers. Clients on the other side receive and store them.
If the service does not receive these headers, it is forced to return the 403 Forbidden status code. If the service receives the headers and they do not match the internal representation of the resource, it must return 412 Precondition Failed. Otherwise, the service returns a success status code like 200 OK. The flow is rendered in the diagram below.
An Example
Let’s assume that the application above has a decoupled interface and storage. The interface is written in JavaScript and the only way it can communicate with the storage is through a RESTful service. This means that, when an article is edited, the client makes a PUT HTTP request to a service, notifying it about the changes performed. In this case, the JavaScript interface acts like a client for the service. So, what happens when users are editing the article at the same time?
First, Mary starts editing the article and gets the last representation of the resource upon receiving the Last-Modified and ETag HTTP headers. The request may look as shown below.
# Request from Mary's client
GET /article?id=12 HTTP/1.1
Host: www.my.wiki.com
# Response
HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Sun, 13 Jan 2013 00:34:12 GMT
ETag: “cbd1956fb32c0275f1faccbb6fafff8f”
...
When Bob starts editing the article, it receives the same representation as Mary, because she has not yet saved the changes. Bob is faster and finishes the changes before Mary. His client sends the following HTTP request to the service:
# Request from Bob's client
PUT /article?id=12 HTTP/1.1
Host: www.my.wiki.com
If-Umodified-Since: Sun, 13 Jan 2013 00:34:12 GMT
If-Match: "cbd1956fb32c0275f1faccbb6fafff8f"
…
# Response status is 200 OK because the representation of the resource is still fresh
HTTP/1.1 200 OK
Content-Type: application/json
...
When Mary finishes her changes, the client that she is using still has the old representation of the resource in memory. The HTTP request made is similar to the one Bob’s client makes, only the body is different. Because in the mean time the resource changed, the 412 Precondition Failed status is returned:
# Request from Mary's client
PUT /article?id=12 HTTP/1.1
Host: www.my.wiki.com
If-Umodified-Since: Sun, 13 Jan 2013 00:34:12 GMT
If-Match: "cbd1956fb32c0275f1faccbb6fafff8f"
…
# Response status is 412 Precondition Failed because Mary edited an old version of the article
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
{ "error":
{ "code": 234,
"message": "You are trying to update a resource that has been modified by another user first."
}
}
So, basically this is the way you can use RESTful services for optimistic concurrency control. In our next tutorial, we will show you how to optimize your GET requests, by avoiding to read the resources if no changes occurred.
And don’t forget, if you want to become a Clouder, you can apply to any of the available positions.
7 Comments
You can post comments in this post.
Great article! The first I found so far that deals with multiple active clients in a restful web service context.
I would be highly interested in the next article you propose: “optimize your GET requests, by avoiding to read the resources if no changes occurred”, especially if you were to address partial updates as well, ie only update records that have been changed to minimize data transfer between the server that stores the model and multiple clients that need to be notified of each other’s changes.
Thanks!
Mattijs Kneppers 12 years ago
The graphs are well put together and very comprehensive
HAMILTON 9 years ago
Thank you very much, it’s really helpful, So I can use ETag to compare two resources if they are updated or not. this will help me in a synchronization project I’m currently working on.
Mohammed Abulsoud 8 years ago
Great article. Thank you very much. Very helpful.
Debdas 8 years ago
Similar concept when dealing with databases, use timestamp / rowversion, but this is good to know that HTTP has specific headers, such as if-whatever and ETag. Thanks for sharing.
Joe Dotnot 8 years ago
There actually is a specific HTTP Status to indicate that conditional request headers are mandatory (and missing): HTTP 428 Precondition Required. [1][2] If your client understands this code or you can make it so, you should probably use 428 instead of the more general 403 in this situation.
[1] https://tools.ietf.org/html/rfc6585#section-3
[2] https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/428
Carsten Milkau 7 years ago
This approach works well if the initial request returns results for a single entity. What if the initial request returns an array of results and the intention is to just update just one of those entities? That would imply the need for an optimistic concurrency token per entity returned, which doesn’t really fit the model described.
Anthony Geoghegan 7 years ago
Post A Reply