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 /users
con los datos {username: 'skwee357', email: '[email protected]'}
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/1
volverá {id: 1, username: 'skwee357', email: '[email protected]'}
.
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/1
con " documento de parche ". En mi caso sería el documento JSON: {email: '[email protected]'}
. 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,
/users
reemplazar toda la colección. Emitir PUT en una entidad específica no tiene sentido después de la introducción de PATCH. ¿Me equivoco?
Respuestas:
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:
Se refiere a una entidad, no a una colección.
La entidad que está proporcionando está completa (la entidad completa ).
Veamos uno de tus ejemplos.
Si envía este documento a
/users
, como sugiere, entonces podría recuperar una entidad comoSi desea modificar esta entidad más tarde, elija entre PUT y PATCH. Un PUT podría verse así:
Puede lograr lo mismo usando PATCH. Eso podría verse así:
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?
(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.
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.
Después de un PARCHE, tiene una entidad modificada:
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.
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.
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.
fuente
GET /users/1
antes de que la Oficina de Correos actualizara el código postal y luego volviera a hacer la mismaGET /users/1
solicitud 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.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 ):
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 /users
devuelve una lista de usuarios, actualmente:En lugar de PATCHing / users / {id}, como en el ejemplo del OP, suponga que el servidor permite PATCHing / users. Emitamos esta solicitud PATCH:
Nuestro documento de revisión le indica al servidor que agregue un nuevo usuario llamado
newuser
a la lista de usuarios. Después de llamar a esto por primera vez,GET /users
volvería: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 /users
devuelve un siguiente :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:
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 nadaf(g(f(x)))
.fuente
/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./users
), cualquier solicitud de PUT debe reemplazar el contenido de esa colección. Por lo tanto, un PUT/users
debe 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/emails
puede ser una colección y puede ser perfectamente válido para permitir reemplazar toda la colección por una nueva.op
acció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 elop
campo active los flujos de trabajo del lado del servidor. En escenarios REST más directos, este tipo deop
funcionalidad es una mala práctica y probablemente debería manejarse directamente a través de verbos HTTP.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
Dado eso, un PUT debería enviar todo el objeto. Por ejemplo,
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.
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.
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/
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.
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.
fuente
La diferencia entre PUT y PATCH es que:
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:
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:
Con la convención anterior, el PATCH en el ejemplo puede tomar la siguiente forma:
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.
fuente
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).
fuente
Permítanme citar y comentar más de cerca la sección 4.2.2 del RFC 7231 , ya citada en comentarios anteriores:
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
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.
fuente
En mi humilde opinión, idempotencia significa:
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.
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.
fuente
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 recursosRes
.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 = x
yPATCH(x: Res, y: Res): Res
, que funciona comoPATCH({a: 2}, {a: 1, b: 3}) == {a: 2, b: 3}
.(Esta definición está específicamente diseñada para discutir
PUT
yPOST
, por ejemplo, no tiene mucho sentidoGET
yPOST
, ya que no le importa la persistencia).Ahora, arreglando
x: Res
(hablando de manera informativa, usando curry),PUT(x: Res)
yPATCH(x: Res)
son funciones univariadas de tipoRes → Res
.Una función
g: Res → Res
se llama a nivel mundial idempotente , cuandog ○ g == g
, es decir, para cualquiery: Res
,g(g(y)) = g(y)
.Deje
x: Res
un recurso, yk = x.keys
. Una funcióng = f(x)
se llama idempotente izquierdo , cuando para cada unay: Res
, tenemosg(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:
t: Set<T> → Map<T, Boolean>
definida conx in A iff t(A)(x) == True
. Usando esta definición, el parche se deja idempotente.{id: 1, email: "[email protected]"}
debe coincidir con{email: "[email protected]"}
, 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:
PUT /user/12 {email: "[email protected]"}
da como resultado{email: "...", version: 1}
la primera vez y{email: "...", version: 2}
la segunda.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.
fuente
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.
fuente