Saltar al contenido

json: uso de métodos PUT vs PATCH en escenarios de la vida real de la API REST

octubre 21, 2021
apple touch icon@2

NOTA: Cuando pasé un tiempo leyendo sobre REST, la idempotencia era un concepto confuso para tratar de acertar. Todavía no lo entendí del todo bien en mi respuesta original, como han demostrado otros comentarios (y la respuesta de Jason Hoetger). Durante un tiempo, me he resistido a actualizar ampliamente esta respuesta para evitar plagiar efectivamente a Jason, pero la estoy editando ahora porque, bueno, me pidieron (en los comentarios).

Después de leer mi respuesta, le sugiero que también lea la excelente respuesta de Jason Hoetger a esta pregunta, y trataré de mejorar mi respuesta sin simplemente robarle a Jason.

¿Por qué PUT es idempotente?

Como señaló en su cita RFC 2616, PUT se considera idempotente. Cuando PONTE un recurso, estas dos suposiciones están en juego:

  1. Te refieres a una entidad, no a una colección.

  2. La entidad que está suministrando está completa (el completo entidad).

Veamos uno de sus ejemplos.

{ "username": "skwee357", "email": "[email protected]" }

Si PUBLICA este documento a /users, como sugiere, entonces podría recuperar una entidad como

## /users/1

{
    "username": "skwee357",
    "email": "[email protected]"
}

Si desea modificar esta entidad más adelante, elija entre PUT y PATCH. Un PUT podría verse así:

PUT /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // new email address
}

Puede lograr lo mismo usando PATCH. Eso podría verse así:

PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

Notarás una diferencia de inmediato entre estos dos. El PUT incluyó todos los parámetros de este usuario, pero PATCH solo incluyó el que se estaba modificando (email).

Al usar PUT, se asume que está enviando la entidad completa, y esa entidad completa reemplaza cualquier entidad existente en ese URI. En el ejemplo anterior, PUT y PATCH logran el mismo objetivo: ambos cambian la dirección de correo electrónico de este usuario. Pero PUT lo maneja reemplazando toda la entidad, mientras que PATCH solo actualiza los campos que se proporcionaron, dejando los demás en paz.

Dado que las solicitudes PUT incluyen toda la entidad, si emite la misma solicitud repetidamente, siempre debería tener el mismo resultado (los datos que envió ahora son los datos completos de la entidad). Por tanto, PUT es idempotente.

Usando PUT incorrecto

¿Qué sucede si usa los datos de PATCH anteriores en una solicitud PUT?

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PUT /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "email": "[email protected]"      // new email address... and nothing else!
}

(Supongo, a los efectos de esta pregunta, que el servidor no tiene ningún campo obligatorio específico y permitiría que esto suceda … puede que ese no sea el caso en la realidad).

Dado que usamos PUT, pero solo suministramos email, ahora eso es lo único en esta entidad. Esto ha provocado la pérdida de datos.

Este ejemplo está aquí con fines ilustrativos; en realidad, nunca lo haga. Esta solicitud PUT es técnicamente idempotente, pero eso no significa que no sea una idea terrible y rota.

¿Cómo puede PATCH ser idempotente?

En el ejemplo anterior, PATCH era idempotente. Hizo un cambio, pero si realizaba el mismo cambio una y otra vez, siempre devolvería el mismo resultado: cambió la dirección de correo electrónico al nuevo valor.

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // email address was changed
}
PATCH /users/1
{
    "email": "[email protected]"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "[email protected]"       // nothing changed since last GET
}

Mi ejemplo original, arreglado para mayor precisión.

Originalmente tenía ejemplos que pensé que mostraban no idempotencia, pero eran engañosos / incorrectos. Voy a mantener los ejemplos, pero los usaré para ilustrar algo diferente: que múltiples documentos PATCH contra la misma entidad, modificando diferentes atributos, no hacen que los PATCH sean no idempotentes.

Digamos que en algún momento pasado, se agregó un usuario. Este es el estado desde el que está comenzando.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Después de un PATCH, tiene una entidad modificada:

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",    // the email changed, yay!
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

Si luego aplica repetidamente su PATCH, continuará obteniendo el mismo resultado: el correo electrónico se cambió al nuevo valor. A entra, A sale, por lo tanto esto es idempotente.

Una hora más tarde, después de que te hayas ido a hacer un café y te tomes un descanso, alguien más viene con su propio PATCH. Parece que la oficina de correos ha realizado algunos cambios.

PATCH /users/1
{"zip": "12345"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",  // still the new email you set
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"                      // and this change as well
}

Dado que este PARCHE de la oficina de correos no se refiere al correo electrónico, solo al código postal, si se aplica repetidamente, también obtendrá el mismo resultado: el código postal se establece en el nuevo valor. A entra, A sale, por lo tanto esto es además idempotente.

Al día siguiente, decides enviar tu PATCH nuevamente.

PATCH /users/1
{"email": "[email protected]"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "[email protected]",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

Su parche tiene el mismo efecto que tuvo ayer: configuró la dirección de correo electrónico. A entró, A salió, por lo tanto, esto también es idempotente.

¿Qué hice mal en mi respuesta original?

Quiero hacer una distinción importante (algo que hice mal en mi respuesta original). Muchos servidores responderán a sus solicitudes REST devolviendo el nuevo estado de la entidad, con sus modificaciones (si las hubiera). Entonces, cuando recibas esto respuesta de vuelta, es diferente del que regresaste ayer, porque el código postal no es el que recibió la última vez. Sin embargo, su solicitud no se refería al código postal, solo al correo electrónico. Entonces, su documento PATCH sigue siendo idempotente: el correo electrónico que envió en PATCH ahora es la dirección de correo electrónico de la entidad.

Entonces, ¿cuándo PATCH no es idempotente?

Para un tratamiento completo de esta pregunta, nuevamente lo remito a la respuesta de Jason Hoetger. Voy a dejarlo así, porque honestamente no creo que pueda responder esta parte mejor de lo que él ya lo ha hecho.

close