How to avoid data conflict between two PUT requests in HTTP?

8

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

Let's imagine the situation:

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

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

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

We have two active users in 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 feature:

[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 be worth R $ 15.00. This way, it fixes 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 your change to update in the database:

[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 own, the description that 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 quoted the PUT method because all resource information was usually sent through the request, not just changed fields, as it would be in PATCH - which would suffer from the same harm if Albert and Bohr changed the same field. p>     

asked by anonymous 04.08.2018 / 00:12

1 answer

7

You can adopt the Optimistic Lock concept to prevent a request from updating a feature using its old information as a reference.

In the HTTP specification there are 2 HTTP headers that can be used in conjunction for this: ETag and If-Match .

ETag

The content of ETag (Entity Tag ) is a unique representation of the current state of the resource. This information can be from a hash (created using MD5, SHA-1, etc.) of the resource information or even some UUID saved with the resource, / p>

Its use works like this: every time you request ( GET ) the resource, you also return the ETag information in the header of the response.

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

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 client should save this information from ETag to send it in PUT .

If-Match

When you change the resource via PUT , you will send the same information that came in Etag using another header, 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

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

After updating the product feature, the b0c396189 value will no longer represent the resource. As I said at the beginning, this value is a unique representation of the resource, so with every update of it this value should change.

Let's say the new value is 096849fba .

Now let's look at the example that, at the same time, someone consulted the same product. This person will still have the old value of Etag with it, b0c396189 . If this attempts to update the product by sending this value b0c396189 to the If-Match header of the request, the server will compare this value with the current value of 096849fba and will detect the difference. At this point, the server will refuse the update, being able to return a 412 Precondition Failed error.

    
04.08.2018 / 01:39