json - resource - restful put example
REST API-PUT vs PATCH con ejemplos de la vida real (7)
TLDR - Versión simplificada
PUT => Establecer todos los atributos nuevos para un recurso existente.
PATCH => Actualizar parcialmente un recurso existente (no se requieren todos los atributos).
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 ese URI es capaz de ser definido como un nuevo recurso por el agente de usuario solicitante, 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 a
/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 que
/users/1
) y cualquier próxima llamada a GET
/users/1
devolverá
{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 PATCH
/users/1
con "
documento de parche
".
En mi caso, sería un 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 emitiría 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 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 para qué se utiliza PUT. Y esto me lleva a la segunda (y principal) pregunta:
-
¿Cuál es la verdadera diferencia entre PUT y PATCH?
He leído en alguna parte que el 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 bajo un URI de recurso específico y por qué dicha operación no se considera como actualización / parcheo de la entidad?
El único caso de uso práctico que veo para PUT es emitir un PUT en la colección, es decir,
/users
para 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?
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 integral 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 El mismo documento de parche cambia el recurso.
Para ilustrar un PATCH no idempotente, suponga que hay un recurso / users, y suponga que llamar a
GET /users
devuelve una lista de usuarios, actualmente:
[{ "id": 1, "username": "firstuser", "email": "[email protected]" }]
En lugar de PATCHing / users / {id}, como en el ejemplo del OP, suponga que el servidor permite PATCHing / users. Emitamos esta solicitud de PARCHE:
PATCH /users
[{ "op": "add", "username": "newuser", "email": "[email protected]" }]
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
devolverían:
[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
{ "id": 2, "username": "newuser", "email": "[email protected]" }]
Ahora, si emitimos
exactamente la misma
solicitud de PATCH que la anterior, ¿qué sucede?
(Por el bien de este ejemplo, supongamos que el recurso / users permite nombres de usuario duplicados). El "op" es "add", por lo que se agrega un nuevo usuario a la lista y un
GET /users
posterior devuelve:
[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
{ "id": 2, "username": "newuser", "email": "[email protected]" },
{ "id": 3, "username": "newuser", "email": "[email protected]" }]
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 ventajas de 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 establece que
f(f(f(x))) = f(x)
, pero no garantiza sobre
f(g(f(x)))
.
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.
La diferencia entre PUT y PATCH es que:
- PUT debe ser idempotente. Para lograr eso, debe poner todo el recurso completo en el cuerpo de la solicitud.
- 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": "[email protected]",
"state": "NY",
"zip": "10001"
}
PATCH /contacts/1
{
[{"operation": "add", "field": "address", "value": "123 main street"},
{"operation": "replace", "field": "email", "value": "[email protected]"},
{"operation": "delete", "field": "zip"}]
}
GET /contacts/1
{
"id": 1,
"name": "Sam Kwee",
"email": "[email protected]",
"state": "NY",
"address": "123 main street",
}
En lugar de utilizar campos explícitos de "operación", el lenguaje de parche puede hacerlo implícito definiendo convenciones como:
en el cuerpo de solicitud PATCH:
- La existencia de un campo significa "reemplazar" o "agregar" ese campo.
- 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": "[email protected]",
"zip":
}
Que se ve más conciso y fácil de usar. Pero los usuarios deben ser conscientes de la convención subyacente.
Con las operaciones que mencioné anteriormente, el PARCHE sigue siendo idempotente. Pero si define operaciones como: "incrementar" o "agregar", puede ver fácilmente que ya no será idempotente.
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 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 que el ejemplo sea un poco más preciso al hacer explícita una intención 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": "[email protected]"}
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 de la 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.
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: ''[email protected]''}
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: ''[email protected]''}
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: ''[email protected]''}
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 Request-URI, 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": "[email protected]" }
]
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, Roy Fielding mismo dijo que PATCH fue algo que [él] creó para la propuesta HTTP / 1.1 inicial 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 perdidos en nulos. Puede que no sea la forma "más correcta" de hacerlo, pero la buena suerte es perfecta.
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.
{ "username": "skwee357", "email": "[email protected]" }
Si envía este documento a
/users
, como sugiere, puede 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 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 los datos completos 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": "[email protected]"
}
PUT /users/1
{
"email": "[email protected]" // new email address
}
GET /users/1
{
"email": "[email protected]" // 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 utilizamos PUT, pero solo proporcionamos
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": "[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, 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 una cosa diferente: que varios 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ás comenzando.
{
"id": 1,
"name": "Sam Kwee",
"email": "[email protected]",
"address": "123 Mockingbird Lane",
"city": "New York",
"state": "NY",
"zip": "10001"
}
Después de un PARCHE, 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 ir 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": "[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 también es 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ó, 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.