Uso de métodos PUT vs PATCH en escenarios de la vida real de API REST


680

En primer lugar, algunas definiciones:

PUT se define en la Sección 9.6 RFC 2616 :

El método PUT solicita que la entidad adjunta se almacene bajo el URI de solicitud proporcionado. Si el URI de solicitud se refiere a un recurso ya existente, la entidad adjunta DEBE considerarse como una versión modificada de la que reside en el servidor de origen . Si el URI de solicitud no apunta a un recurso existente y el agente de usuario solicitante puede definir ese URI como un nuevo recurso, el servidor de origen puede crear el recurso con ese URI.

PATCH se define en RFC 5789 :

El método PATCH solicita que se aplique un conjunto de cambios descritos en la entidad de solicitud al recurso identificado por el URI de solicitud.

También de acuerdo con RFC 2616 Sección 9.1.2 PUT es Idempotente mientras que PATCH no lo es.

Ahora echemos un vistazo a un ejemplo real. Cuando hago POST /userscon los datos {username: 'skwee357', email: 'skwee357@domain.com'}y el servidor es capaz de crear un recurso, responderá con 201 y la ubicación del recurso (supongamos /users/1) y cualquier próxima llamada a GET /users/1volverá {id: 1, username: 'skwee357', email: 'skwee357@domain.com'}.

Ahora digamos que quiero modificar mi correo electrónico. La modificación del correo electrónico se considera "un conjunto de cambios" y, por lo tanto, debería PATCHAR /users/1con " documento de parche ". En mi caso sería el documento JSON: {email: 'skwee357@newdomain.com'}. El servidor luego devuelve 200 (suponiendo que el permiso esté bien). Esto me lleva a la primera pregunta:

  • PARCHE NO es idempotente. Lo dijo en RFC 2616 y RFC 5789. Sin embargo, si emito la misma solicitud PATCH (con mi nuevo correo electrónico), obtendré el mismo estado de recurso (con mi correo electrónico modificado al valor solicitado). ¿Por qué PATCH no es entonces idempotente?

PATCH es un verbo relativamente nuevo (RFC introducido en marzo de 2010) y trata de resolver el problema de "parchear" o modificar un conjunto de campos. Antes de presentar PATCH, todos usaban PUT para actualizar los recursos. Pero después de que se introdujo PATCH, me deja confundido sobre para qué se utiliza PUT. Y esto me lleva a mi segunda (y principal) pregunta:

  • ¿Cuál es la diferencia real entre PUT y PATCH? He leído en alguna parte que PUT podría usarse para reemplazar toda la entidad bajo un recurso específico, por lo que uno debería enviar la entidad completa (en lugar del conjunto de atributos como con PATCH). ¿Cuál es el uso práctico real para tal caso? ¿Cuándo le gustaría reemplazar / sobrescribir una entidad en un URI de recurso específico y por qué tal operación no se considera actualizar / parchar la entidad? El único caso de uso práctico que veo para PUT es emitir un PUT en una colección, es decir, /usersreemplazar toda la colección. Emitir PUT en una entidad específica no tiene sentido después de la introducción de PATCH. ¿Me equivoco?

1
a) es RFC 2616, no 2612. b) RFC 2616 ha quedado obsoleto, la especificación actual de PUT está en greenbytes.de/tech/webdav/rfc7231.html#PUT , c) No recibo su pregunta; ¿No es bastante obvio que PUT se puede usar para reemplazar cualquier recurso, no solo una colección, d) antes de que se introdujera PATCH, la gente solía usar POST, e) finalmente, sí, una solicitud de PATCH específica (dependiendo del formato del parche) puede ser idempotente; es solo que no es generalmente.
Julian Reschke

si ayuda, he escrito un artículo sobre PATCH vs PUT eq8.eu/blogs/36-patch-vs-put-and-the-patch-json-syntax-war
equivalente8

55
Simple: POST crea un elemento en una colección. PUT reemplaza un artículo. PATCH modifica un artículo. Al enviar, la URL para el nuevo elemento se calcula y se devuelve en la respuesta, mientras que PUT y PATCH requieren una URL en la solicitud. ¿Derecha?
Tom Russell

Esta publicación puede ser útil: POST vs PUT vs PATCH: mscharhag.com/api-design/http-post-put-patch
micha

Respuestas:


941

NOTA : Cuando pasé tiempo leyendo sobre REST, la idempotencia era un concepto confuso para tratar de acertar. Todavía no lo entendí bien en mi respuesta original, como lo han demostrado otros comentarios (y la respuesta de Jason Hoetger ). Por un tiempo, me he resistido a actualizar esta respuesta ampliamente, para evitar plagiar efectivamente a Jason, pero lo estoy editando ahora porque, bueno, me lo 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é es PUT idempotente?

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

  1. Se refiere a una entidad, no a una colección.

  2. La entidad que está proporcionando está completa (la entidad completa ).

Veamos uno de tus ejemplos.

{ "username": "skwee357", "email": "skwee357@domain.com" }

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

## /users/1

{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}

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

PUT /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // new email address
}

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

PATCH /users/1
{
    "email": "skwee357@gmail.com"       // 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 supone que está enviando la entidad completa, y esa entidad completa reemplaza a 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 a los demás solos.

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

Usando PUT incorrecto

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

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@domain.com"
}
PUT /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "email": "skwee357@gmail.com"      // new email address... and nothing else!
}

(Asumo a los fines de esta pregunta que el servidor no tiene campos obligatorios específicos, y permitiría que esto suceda ... eso puede no ser el caso en realidad).

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

Este ejemplo está aquí con fines ilustrativos, nunca lo hagas. 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. Realizó un cambio, pero si realizó 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": "skwee357@domain.com"
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // email address was changed
}
PATCH /users/1
{
    "email": "skwee357@gmail.com"       // new email address... again
}

GET /users/1
{
    "username": "skwee357",
    "email": "skwee357@gmail.com"       // nothing changed since last GET
}

Mi ejemplo original, corregido por 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 varios documentos de 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ás comenzando.

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "10001"
}

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

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",    // 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 hayas ido a tomar un café y tomar un descanso, alguien más viene con su propio PARCHE. Parece que la oficina de correos ha estado haciendo algunos cambios.

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

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",  // 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 también es idempotente.

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

PATCH /users/1
{"email": "skwee357@newdomain.com"}

{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@newdomain.com",
  "address": "123 Mockingbird Lane",
  "city": "New York",
  "state": "NY",
  "zip": "12345"
}

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

Lo que me equivoqué en mi respuesta original

Quiero hacer una distinción importante (algo que me equivoqué en mi respuesta original). Muchos servidores responderán a sus solicitudes REST enviando el nuevo estado de la entidad, con sus modificaciones (si corresponde). Entonces, cuando reciba esta respuesta , es diferente de la que recibió ayer , porque el código postal no es el que recibió la última vez. Sin embargo, su solicitud no estaba relacionada con el código postal, solo con el correo electrónico. Por lo tanto, 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 . Solo voy a dejarlo así, porque honestamente no creo que pueda responder esta parte mejor de lo que ya lo ha hecho.


2
Esta oración no es del todo correcta: "Pero es idempotente: cada vez que A entra, B siempre sale". Por ejemplo, si fuera GET /users/1antes de que la Oficina de Correos actualizara el código postal y luego volviera a hacer la misma GET /users/1solicitud después de la actualización de la Oficina de Correos, obtendría dos respuestas diferentes (diferentes códigos postales). Entra la misma "A" (solicitud GET), pero está obteniendo resultados diferentes. Sin embargo, GET sigue siendo idempotente.
Jason Hoetger

@JasonHoetger GET es seguro (se supone que no causa ningún cambio), pero no siempre es idempotente. Hay una diferencia. Ver RFC 2616 sec. 9.1 .
Dan Lowe

1
@DanLowe: GET definitivamente está garantizado para ser idempotente. Dice exactamente que en la Sección 9.1.2 de RFC 2616, y en la especificación actualizada, RFC 7231 sección 4.2.2 , que "De los métodos de solicitud definidos por esta especificación, PUT, DELETE y los métodos de solicitud segura son idempotentes". La idempotencia simplemente no significa "obtienes la misma respuesta cada vez que haces la misma solicitud". 7231 4.2.2 continúa diciendo: "Repetir la solicitud tendrá el mismo efecto deseado, incluso si la solicitud original tuvo éxito, aunque la respuesta podría diferir "
Jason Hoetger,

1
@JasonHoetger Lo admitiré, pero no veo qué tiene que ver con esta respuesta, que discutió PUT y PATCH y nunca menciona GET ...
Dan Lowe

1
Ah, el comentario de @JasonHoetger lo aclaró: solo los estados resultantes, en lugar de las respuestas, de múltiples solicitudes de métodos idempotentes deben ser idénticos.
Tom Russell

328

Aunque la excelente respuesta de Dan Lowe respondió muy a fondo la pregunta del OP sobre la diferencia entre PUT y PATCH, su respuesta a la pregunta de por qué PATCH no es idempotente no es del todo correcta.

Para mostrar por qué PATCH no es idempotente, es útil comenzar con la definición de idempotencia (de Wikipedia ):

El término idempotente se usa de manera más exhaustiva para describir una operación que producirá los mismos resultados si se ejecuta una o varias veces [...] Una función idempotente es aquella que tiene la propiedad f (f (x)) = f (x) para cualquier valor x.

En un lenguaje más accesible, un PATCH idempotente podría definirse como: Después de PATCHAR un recurso con un documento de parche, todas las llamadas posteriores de PATCH al mismo recurso con el mismo documento de parche no cambiarán el recurso.

Por el contrario, una operación no idempotente es aquella en la que f (f (x))! = F (x), que para PATCH podría indicarse como: Después de PATCHAR un recurso con un documento de parche, PATCH posterior llama al mismo recurso con el mismo documento parche de hacer cambiar el recurso.

Para ilustrar un PATCH no idempotente, suponga que hay un recurso / users, y suponga que la llamada GET /usersdevuelve una lista de usuarios, actualmente:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" }]

En lugar de PATCHing / users / {id}, como en el ejemplo del OP, suponga que el servidor permite PATCHing / users. Emitamos esta solicitud PATCH:

PATCH /users
[{ "op": "add", "username": "newuser", "email": "newuser@example.org" }]

Nuestro documento de revisión le indica al servidor que agregue un nuevo usuario llamado newusera la lista de usuarios. Después de llamar a esto por primera vez, GET /usersvolvería:

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" }]

Ahora, si emitimos exactamente la misma solicitud de PATCH que la anterior, ¿qué sucede? (En aras de este ejemplo, supongamos que el recurso / users permite nombres de usuario duplicados). La "op" es "add", por lo que se agrega un nuevo usuario a la lista y se GET /usersdevuelve un siguiente :

[{ "id": 1, "username": "firstuser", "email": "firstuser@example.org" },
 { "id": 2, "username": "newuser", "email": "newuser@example.org" },
 { "id": 3, "username": "newuser", "email": "newuser@example.org" }]

El recurso / users ha cambiado nuevamente , a pesar de que emitimos exactamente el mismo PATCH contra el mismo punto final. Si nuestro PATCH es f (x), f (f (x)) no es lo mismo que f (x) y, por lo tanto, este PATCH en particular no es idempotente .

Aunque no se garantiza que PATCH sea ​​idempotente, no hay nada en la especificación PATCH que le impida hacer idempotentes todas las operaciones de PATCH en su servidor particular. RFC 5789 incluso anticipa las ventajas de las solicitudes de parches idempotentes:

Se puede emitir una solicitud PATCH de tal manera que sea idempotente, lo que también ayuda a evitar malos resultados de colisiones entre dos solicitudes PATCH en el mismo recurso en un período de tiempo similar.

En el ejemplo de Dan, su operación PATCH es, de hecho, idempotente. En ese ejemplo, la entidad / users / 1 cambió entre nuestras solicitudes PATCH, pero no debido a nuestras solicitudes PATCH; En realidad, fue el documento de parche diferente de la oficina de correos lo que causó el cambio del código postal. El PATCH diferente de la oficina de correos es una operación diferente; si nuestro PATCH es f (x), el PATCH de la oficina de correos es g (x). La idempotencia dice eso f(f(f(x))) = f(x), pero no garantiza nada f(g(f(x))).


11
Suponiendo que el servidor también permite emitir PUT en /users, esto también haría que PUT no sea idempotente. Todo esto se reduce a cómo está diseñado el servidor para manejar las solicitudes.
Uzair Sajid

13
Por lo tanto, podríamos construir una API solo con las operaciones PATCH. Entonces, ¿en qué se convierte el principio REST de usar http VERBS para realizar acciones CRUD en los recursos? ¿No estamos sobrecomplejando el parche que limita con los caballeros aquí?
bohr

66
Si PUT se implementa en una colección (por ejemplo /users), cualquier solicitud de PUT debe reemplazar el contenido de esa colección. Por lo tanto, un PUT /usersdebe esperar una colección de usuarios y eliminar todos los demás. Esto es idempotente. No es probable que haga algo así en un punto final / users. Pero algo como /users/1/emailspuede ser una colección y puede ser perfectamente válido para permitir reemplazar toda la colección por una nueva.
Vectorjohn

55
Aunque esta respuesta proporciona un gran ejemplo de idempotencia, creo que esto puede enturbiar las aguas en los escenarios REST típicos. En este caso, tiene una solicitud PATCH con una opacción adicional que activa una lógica específica del lado del servidor. Esto requeriría que el servidor y el cliente conozcan los valores específicos que se deben pasar para que el opcampo active los flujos de trabajo del lado del servidor. En escenarios REST más directos, este tipo de opfuncionalidad es una mala práctica y probablemente debería manejarse directamente a través de verbos HTTP.
ivandov

77
Nunca consideraría emitir un PATCH, solo POST y DELETE, contra una colección. ¿Esto realmente se ha hecho alguna vez? ¿Por lo tanto, PATCH puede considerarse idempotente para todos los fines prácticos?
Tom Russell

72

Tenía curiosidad sobre esto también y encontré algunos artículos interesantes. Es posible que no responda su pregunta en toda su extensión, pero esto al menos proporciona más información.

http://restful-api-design.readthedocs.org/en/latest/methods.html

El HTTP RFC especifica que PUT debe tomar una representación de recursos completamente nueva como entidad de solicitud. Esto significa que si, por ejemplo, solo se proporcionan ciertos atributos, estos deberían eliminarse (es decir, establecerse en nulo).

Dado eso, un PUT debería enviar todo el objeto. Por ejemplo,

/users/1
PUT {id: 1, username: 'skwee357', email: 'newemail@domain.com'}

Esto actualizaría efectivamente el correo electrónico. La razón por la cual PUT puede no ser demasiado efectivo es que su única modificación real de un campo e incluir el nombre de usuario es algo inútil. El siguiente ejemplo muestra la diferencia.

/users/1
PUT {id: 1, email: 'newemail@domain.com'}

Ahora, si el PUT se diseñó de acuerdo con las especificaciones, el PUT establecería el nombre de usuario en nulo y obtendría lo siguiente.

{id: 1, username: null, email: 'newemail@domain.com'}

Cuando usa un PATCH, solo actualiza el campo que especifique y deja el resto solo como en su ejemplo.

La siguiente versión del PATCH es un poco diferente de lo que nunca había visto antes.

http://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/

La diferencia entre las solicitudes PUT y PATCH se refleja en la forma en que el servidor procesa la entidad adjunta para modificar el recurso identificado por el URI de solicitud. En una solicitud PUT, la entidad adjunta se considera una versión modificada del recurso almacenado en el servidor de origen, y el cliente solicita que se reemplace la versión almacenada. Sin embargo, con PATCH, la entidad adjunta contiene un conjunto de instrucciones que describen cómo se debe modificar un recurso que actualmente reside en el servidor de origen para producir una nueva versión. El método PATCH afecta el recurso identificado por el URI de solicitud, y también PUEDE tener efectos secundarios en otros recursos; es decir, se pueden crear nuevos recursos o modificar los existentes mediante la aplicación de un PATCH.

PATCH /users/123

[
    { "op": "replace", "path": "/email", "value": "new.email@example.org" }
]

Está más o menos tratando el PATCH como una forma de actualizar un campo. Entonces, en lugar de enviar el objeto parcial, está enviando la operación. es decir, reemplazar el correo electrónico con valor.

El artículo termina con esto.

Vale la pena mencionar que PATCH no está realmente diseñado para APIs REST verdaderas, ya que la disertación de Fielding no define ninguna forma de modificar parcialmente los recursos. Pero, el propio Roy Fielding dijo que PATCH fue algo que [él] creó para la propuesta inicial HTTP / 1.1 porque el PUT parcial nunca es RESTful. Seguro que no está transfiriendo una representación completa, pero REST no requiere que las representaciones estén completas de todos modos.

Ahora, no sé si estoy particularmente de acuerdo con el artículo, como señalan muchos comentaristas. Enviar una representación parcial puede ser fácilmente una descripción de los cambios.

Para mí, estoy mezclado con el uso de PATCH. En su mayor parte, trataré a PUT como un PARCHE ya que la única diferencia real que he notado hasta ahora es que PUT "debería" establecer los valores faltantes en nulos. Puede que no sea la forma 'más correcta' de hacerlo, pero la buena suerte es perfecta.


77
Puede valer la pena agregar: en el artículo de William Durand (y rfc 6902) hay ejemplos donde "op" es "add". Esto obviamente no es idempotente.
Johannes Brodwall

2
O puede hacerlo más fácil y usar el RFC 7396 Merge Patch en su lugar y evitar construir el parche JSON.
Piotr Kula

para las tablas nosql, las diferencias entre patch y put son importantes, porque nosql no tiene columnas
stackdave

18

La diferencia entre PUT y PATCH es que:

  1. PUT debe ser idempotente. Para lograr eso, debe poner todo el recurso completo en el cuerpo de la solicitud.
  2. PARCHE puede ser no idempotente. Lo que implica que también puede ser idempotente en algunos casos, como los casos que describió.

PATCH requiere un poco de "lenguaje de parche" para decirle al servidor cómo modificar el recurso. La persona que llama y el servidor necesitan definir algunas "operaciones" como "agregar", "reemplazar", "eliminar". Por ejemplo:

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "skwee357@olddomain.com",
  "state": "NY",
  "zip": "10001"
}

PATCH /contacts/1
{
 [{"operation": "add", "field": "address", "value": "123 main street"},
  {"operation": "replace", "field": "email", "value": "abc@myemail.com"},
  {"operation": "delete", "field": "zip"}]
}

GET /contacts/1
{
  "id": 1,
  "name": "Sam Kwee",
  "email": "abc@myemail.com",
  "state": "NY",
  "address": "123 main street",
}

En lugar de usar campos explícitos de "operación", el lenguaje de parche puede hacerlo implícito definiendo convenciones como:

en el cuerpo de solicitud PATCH:

  1. La existencia de un campo significa "reemplazar" o "agregar" ese campo.
  2. Si el valor de un campo es nulo, significa eliminar ese campo.

Con la convención anterior, el PATCH en el ejemplo puede tomar la siguiente forma:

PATCH /contacts/1
{
  "address": "123 main street",
  "email": "abc@myemail.com",
  "zip":
}

Que se ve más conciso y fácil de usar. Pero los usuarios deben conocer la convención subyacente.

Con las operaciones que mencioné anteriormente, el PATCH sigue siendo idempotente. Pero si define operaciones como: "incrementar" o "agregar", puede ver fácilmente que ya no será idempotente.


7

TLDR - Versión simplificada

PUT => Establecer todos los nuevos atributos para un recurso existente.

PATCH => Actualizar parcialmente un recurso existente (no se requieren todos los atributos).


3

Permítanme citar y comentar más de cerca la sección 4.2.2 del RFC 7231 , ya citada en comentarios anteriores:

Un método de solicitud se considera "idempotente" si el efecto previsto en el servidor de múltiples solicitudes idénticas con ese método es el mismo que el efecto de una sola solicitud de este tipo. De los métodos de solicitud definidos por esta especificación, PUT, DELETE y los métodos de solicitud seguros son idempotentes.

(...)

Los métodos idempotentes se distinguen porque la solicitud puede repetirse automáticamente si ocurre una falla de comunicación antes de que el cliente pueda leer la respuesta del servidor. Por ejemplo, si un cliente envía una solicitud PUT y la conexión subyacente se cierra antes de recibir cualquier respuesta, el cliente puede establecer una nueva conexión y volver a intentar la solicitud idempotente. Sabe que repetir la solicitud tendrá el mismo efecto deseado, incluso si la solicitud original tuvo éxito, aunque la respuesta puede ser diferente.

Entonces, ¿qué debería ser "lo mismo" después de una solicitud repetida de un método idempotente? No es el estado del servidor, ni la respuesta del servidor, sino el efecto deseado . En particular, el método debe ser idempotente "desde el punto de vista del cliente". Ahora, creo que este punto de vista muestra que el último ejemplo en la respuesta de Dan Lowe , que no quiero plagiar aquí, de hecho muestra que una solicitud de PARCHE puede ser no idempotente (de una manera más natural que el ejemplo en La respuesta de Jason Hoetger ).

De hecho, hagamos el ejemplo un poco más preciso haciendo explícito un intento posible para el primer cliente. Digamos que este cliente revisa la lista de usuarios con el proyecto para verificar sus correos electrónicos y códigos postales. Comienza con el usuario 1, se da cuenta de que el código postal es correcto pero el correo electrónico es incorrecto. Decide corregir esto con una solicitud PATCH, que es totalmente legítima, y ​​solo envía

PATCH /users/1
{"email": "skwee357@newdomain.com"}

ya que esta es la única corrección. Ahora, la solicitud falla debido a algún problema de red y se vuelve a enviar automáticamente un par de horas más tarde. Mientras tanto, otro cliente ha modificado (erróneamente) el código postal del usuario 1. Luego, enviar la misma solicitud PATCH por segunda vez no logra el efecto deseado del cliente, ya que terminamos con un código postal incorrecto. Por lo tanto, el método no es idempotente en el sentido del RFC.

Si, en cambio, el cliente usa una solicitud PUT para corregir el correo electrónico, enviando al servidor todas las propiedades del usuario 1 junto con el correo electrónico, su efecto previsto se logrará incluso si la solicitud tiene que reenviarse más tarde y el usuario 1 ha sido modificado mientras tanto --- ya que la segunda solicitud PUT sobrescribirá todos los cambios desde la primera solicitud.


2

En mi humilde opinión, idempotencia significa:

  • PONER:

Envío una definición de recurso de competencia, por lo tanto, el estado del recurso resultante es exactamente como lo definen los parámetros PUT. Cada vez que actualizo el recurso con los mismos parámetros PUT, el estado resultante es exactamente el mismo.

  • PARCHE:

Envié solo una parte de la definición del recurso, por lo que podría suceder que otros usuarios estén actualizando los OTROS parámetros de este recurso mientras tanto. En consecuencia, parches consecutivos con los mismos parámetros y sus valores pueden resultar con un estado de recursos diferente. Por ejemplo:

Presume un objeto definido de la siguiente manera:

COCHE: - color: negro, - tipo: sedán, - asientos: 5

Lo parcheo con:

{color rojo'}

El objeto resultante es:

COCHE: - color: rojo, - tipo: sedán, - asientos: 5

Luego, algunos otros usuarios parchan este auto con:

{tipo: 'hatchback'}

entonces, el objeto resultante es:

COCHE: - color: rojo, - tipo: hatchback, - asientos: 5

Ahora, si vuelvo a parchear este objeto con:

{color rojo'}

El objeto resultante es:

COCHE: - color: rojo, - tipo: hatchback, - asientos: 5

¡Qué es DIFERENTE a lo que tengo anteriormente!

Es por eso que PATCH no es idempotente mientras que PUT es idempotente.


1

Para concluir la discusión sobre la idempotencia, debo señalar que se puede definir la idempotencia en el contexto REST de dos maneras. Primero formalicemos algunas cosas:

Un recurso es una función con su codominio siendo la clase de cadenas. En otras palabras, un recurso es un subconjunto de String × Any, donde todas las claves son únicas. Llamemos a la clase de los recursos Res.

Una operación REST sobre recursos, es una función f(x: Res, y: Res): Res. Dos ejemplos de operaciones REST son:

  • PUT(x: Res, y: Res): Res = xy
  • PATCH(x: Res, y: Res): Res, que funciona como PATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}.

(Esta definición está específicamente diseñada para discutir PUTy POST, por ejemplo, no tiene mucho sentido GETy POST, ya que no le importa la persistencia).

Ahora, arreglando x: Res(hablando de manera informativa, usando curry), PUT(x: Res)y PATCH(x: Res)son funciones univariadas de tipo Res → Res.

  1. Una función g: Res → Resse llama a nivel mundial idempotente , cuando g ○ g == g, es decir, para cualquier y: Res, g(g(y)) = g(y).

  2. Deje x: Resun recurso, y k = x.keys. Una función g = f(x)se llama idempotente izquierdo , cuando para cada una y: Res, tenemos g(g(y))|ₖ == g(y)|ₖ. Básicamente significa que el resultado debería ser el mismo, si miramos las claves aplicadas.

Entonces, PATCH(x)no es idempotente a nivel mundial, sino que se deja idempotente. Y la idempotencia izquierda es lo que importa aquí: si aplicamos parches a algunas claves del recurso, queremos que esas claves sean las mismas si la reparamos nuevamente, y no nos importa el resto del recurso.

Y cuando RFC está hablando de que PATCH no es idempotente, está hablando de idempotencia global. Bueno, es bueno que no sea idempotente a nivel mundial, de lo contrario habría sido una operación rota.


Ahora, la respuesta de Jason Hoetger está tratando de demostrar que PATCH ni siquiera se deja idempotente, pero está rompiendo demasiadas cosas para hacerlo:

  • En primer lugar, PATCH se usa en un conjunto, aunque PATCH se define para trabajar en mapas / diccionarios / objetos clave-valor.
  • Si alguien realmente quiere aplicar PATCH a conjuntos, entonces hay una traducción natural que debería usarse:, t: Set<T> → Map<T, Boolean>definida con x in A iff t(A)(x) == True. Usando esta definición, el parche se deja idempotente.
  • En el ejemplo, esta traducción no se usó, en cambio, el PATCH funciona como un POST. En primer lugar, ¿por qué se genera una ID para el objeto? ¿Y cuándo se genera? Si el objeto se compara por primera vez con los elementos del conjunto, y si no se encuentra ningún objeto coincidente, se genera la ID, entonces nuevamente el programa debería funcionar de manera diferente ( {id: 1, email: "me@site.com"}debe coincidir con {email: "me@site.com"}, de lo contrario, el programa siempre se rompe y el PATCH no puede parche). Si se genera la ID antes de comparar con el conjunto, nuevamente el programa se interrumpe.

Uno puede hacer ejemplos de PUT que no sea idempotente con romper la mitad de las cosas que se rompen en este ejemplo:

  • Un ejemplo con características adicionales generadas sería el versionado. Uno puede mantener un registro de la cantidad de cambios en un solo objeto. En este caso, PUT no es idempotente: PUT /user/12 {email: "me@site.com"}da como resultado {email: "...", version: 1}la primera vez y {email: "...", version: 2}la segunda.
  • Al jugar con las ID, uno puede generar una nueva ID cada vez que se actualiza el objeto, lo que resulta en un PUT no idempotente.

Todos los ejemplos anteriores son ejemplos naturales que uno puede encontrar.


Mi punto final es que PATCH no debe ser idempotente a nivel mundial , de lo contrario no le dará el efecto deseado. Desea cambiar la dirección de correo electrónico de su usuario, sin tocar el resto de la información, y no desea sobrescribir los cambios de otra parte que acceda al mismo recurso.


-1

Una información adicional que solo debo agregar es que una solicitud PATCH usa menos ancho de banda en comparación con una solicitud PUT ya que solo una parte de los datos se envía, no toda la entidad. Tan solo use una solicitud PATCH para actualizaciones de registros específicos como (1-3 registros) mientras PUT solicita para actualizar una mayor cantidad de datos. Eso es todo, no pienses demasiado ni te preocupes demasiado.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.