How to avoid conflict of data between two PUT requests on HTTP?

Asked

Viewed 139 times

8

What techniques can we use to avoid the collision between data of two PUT requests so that the changes of the second request do not override those of the first?

Let’s imagine the situation:

  • There is a product registered in a virtual store system named "Bom Bacana Product", without description and unit price R $ 10,00.

The JSON that would represent the resource is presented below, accessed via /produto/1.

{
  "id": 1,
  "name": "Produto Bem Bacana",
  "description": "",
  "price": 10.00
}

We have two active users on the system: Albert and Bohr; both access the resource. Staying then:

[Banco de dados]                       [Albert]                              [Borh]

{                                      {                                     {
  "id": 1,                               "id": 1,                              "id": 1,
  "name": "Produto Bem Bacana",          "name": "Produto Bem Bacana",         "name": "Produto Bem Bacana",
  "description": "",                     "description": "",                    "description": "",
  "price": 10.00                         "price": 10.00                        "price": 10.00
}                                      }                                     }

At this point, Albert realizes that the product is without description and decides to add it.

[Banco de dados]                       [Albert]                              [Borh]

{                                      {                                     {
  "id": 1,                               "id": 1,                              "id": 1,
  "name": "Produto Bem Bacana",          "name": "Produto Bem Bacana",         "name": "Produto Bem Bacana",
  "description": "",                     "description": "É bacana mesmo",      "description": "",
  "price": 10.00                         "price": 10.00                        "price": 10.00
}                                      }                                     }

Albert then submits to the database, updating the resource:

[Banco de dados]                       [Albert]                              [Borh]

{                                      {                                     {
  "id": 1,                               "id": 1,                              "id": 1,
  "name": "Produto Bem Bacana",          "name": "Produto Bem Bacana",         "name": "Produto Bem Bacana",
  "description": "É bacana mesmo",       "description": "É bacana mesmo",      "description": "",
  "price": 10.00                         "price": 10.00                        "price": 10.00
}                                      }                                     }

Borh realizes that the price of the product is wrong. Instead of R $ 10,00, the product should value R $ 15,00. This way, it corrects the product:

[Banco de dados]                       [Albert]                              [Borh]

{                                      {                                     {
  "id": 1,                               "id": 1,                              "id": 1,
  "name": "Produto Bem Bacana",          "name": "Produto Bem Bacana",         "name": "Produto Bem Bacana",
  "description": "É bacana mesmo",       "description": "É bacana mesmo",      "description": "",
  "price": 10.00                         "price": 10.00                        "price": 15.00
}                                      }                                     }

Borh then submits his change to update in the bank:

[Banco de dados]                       [Albert]                              [Borh]

{                                      {                                     {
  "id": 1,                               "id": 1,                              "id": 1,
  "name": "Produto Bem Bacana",          "name": "Produto Bem Bacana",         "name": "Produto Bem Bacana",
  "description": "",                     "description": "É bacana mesmo",      "description": "",
  "price": 15.00                         "price": 10.00                        "price": 15.00
}                                      }                                     }

And it turns out that Bohr’s information was outdated after Albert made his changes. By the time Bohr submits his, the description Albert had added is lost.

So what techniques can be applied to prevent this data collision? How could it be done to identify, at the time of Bohr’s submission, that the resource has been updated and that its information is outdated, preventing its changes from overwriting Albert’s?

I cited the PUT method because, generally, all resource information is sent through the request, not only changed fields, as it would be in PATCH - which would suffer the same harm if Albert and Bohr changed the same field.

1 answer

8


You can adopt the concept of Lock Optimist to prevent a request from updating a resource using its old information as a reference.

In the HTTP specification there are 2 headers (Headers) HTTP that can be used together for this: Etag and the If-Match.

Etag

The content of ETag (Entity Tag) is a unique representation of the current state of the resource. This information can be provided from a hash (created using MD5, SHA-1, etc) resource information or even some UUID except with the feature, which is changed with each update of the same.

Your use works as follows: every time you request (GET) the resource, you also return in header of its ETag.

Let’s say the current representation of the resource is the value b0c396189:

GET /api/produtos/1

Response:
{
  "id": 1,
  "name": "Produto Bem Bacana",
  "description": "",
  "price": 10.00
}

Response Headers:
content-type: application/json
... demais cabeçalhos de response
ETag: b0c396189

Once this is done, the customer must store this information from ETag to send her on PUT.

If-Match

By changing the resource via PUT, you will send the same information that came in Etag using another header, the If-Match:

PUT /api/produtos/1
Request:
{
  "id": 1,
  "name": "Produto Bem Bacana",
  "description": "Descrição",
  "price": 10.00
}

Request Headers
content-type: application/json
... demais cabeçalhos de request
If-Match: b0c396189

Once this is done, the server will receive this information and compare if the current value of the resource (b0c396189) on the server is the same that came in the header If-Match. If the values are the same, it means that the resource obtained in the GET by the client remains the same as the server and that, thus, the update can be performed.

After the product feature update, the value b0c396189 no longer represents the resource. As I said at the beginning, this value is a unique representation of the resource, so every update of it this value must change.

Let’s say the new value is 096849fba.

Now let’s take the example that, at the same time, someone consulted the same product. This person will still have the old value of Etag with him, the b0c396189. If this tries to make a product update by sending this amount b0c396189 in the header If-Match request, the server will compare this value to the current value of 096849fba and will detect the difference. At this time, the server will refuse the update and may return an error 412 Precondition Failed.

  • 2

    Do you have a link for documentation of this? Something official or not?

  • 2

    @Jeffersonquesado, has yes, RFC. Better than this, there is no hehehe. I will add the links.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.