Solución:
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:
-
Te refieres a una entidad, no a una colección.
-
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 a 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.
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 completa 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 parchear un recurso con un documento de parche, todas las llamadas de PATCH posteriores 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 expresarse como: Después de parchear un recurso con un documento de parche, las siguientes llamadas de PATCH al mismo recurso con el mismo documento de parche hacer cambiar el recurso.
Para ilustrar un PATCH no idempotente, suponga que hay un recurso / users, y suponga que llamar 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 PATCH:
PATCH /users
[{ "op": "add", "username": "newuser", "email": "[email protected]" }]
Nuestro documento de parche 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:
[{ "id": 1, "username": "firstuser", "email": "[email protected]" },
{ "id": 2, "username": "newuser", "email": "[email protected]" }]
Ahora, si emitimos el exactamente el mismo Solicitud de PATCH como arriba, ¿qué sucede? (Por el bien de este ejemplo, supongamos que el recurso / users permite nombres de usuario duplicados). La “op” es “agregar”, por lo que se agrega un nuevo usuario a la lista y un GET /users
devoluciones:
[{ "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 de nuevo, a pesar de que emitimos el exactamente el mismo PARCHE contra el exactamente 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 PARCHE en particular no es idempotente.
Aunque PATCH no garantizado para ser idempotente, no hay nada en la especificación PATCH que le impida hacer idempotentes todas las operaciones PATCH en su servidor particular. RFC 5789 incluso anticipa las ventajas de las solicitudes de PATCH idempotentes:
Se puede emitir una solicitud de PATCH de tal manera que sea idempotente, lo que también ayuda a prevenir malos resultados de colisiones entre dos solicitudes de 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 de PATCH, pero no porque nuestras solicitudes de PATCH; en realidad era la oficina de correos diferente documento de parche que provocó 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 afirma que f(f(f(x))) = f(x)
, pero no da garantías sobre f(g(f(x)))
.
También tenía curiosidad por esto y encontré algunos artículos interesantes. Puede que no responda a 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
La RFC de HTTP especifica que PUT debe tomar una representación de recurso completamente nueva como entidad de solicitud. Esto significa que si, por ejemplo, solo se proporcionan ciertos atributos, estos deben eliminarse (es decir, establecerlos como nulos).
Dado eso, entonces un PUT debería enviar el objeto completo. 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 que PUT puede no ser demasiado eficaz 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 la especificación, entonces 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 especifica y deja el resto solo como en su ejemplo.
La siguiente versión del PATCH es un poco diferente a la 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 la Request-URI. 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 reside actualmente en el servidor de origen para producir una nueva versión. El método PATCH afecta el recurso identificado por 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á tratando más o menos el PATCH como una forma de actualizar un campo. Entonces, en lugar de enviar el objeto parcial, está enviando la operación. es decir, reemplace el correo electrónico con valor.
El artículo termina con esto.
Vale la pena mencionar que PATCH no está realmente diseñado para API REST realmente, ya que la disertación de Fielding no define ninguna forma de modificar parcialmente los recursos. Pero, el propio Roy Fielding dijo que PATCH era algo [he] creado para la propuesta HTTP / 1.1 inicial porque 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 bien, 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 en el uso de PATCH. En su mayor parte, trataré PUT como un PATCH, ya que la única diferencia real que he notado hasta ahora es que PUT “debería” establecer los valores faltantes en nulo. Puede que no sea el ‘más forma correcta de hacerlo, pero buena suerte con la codificación perfecta.