¿Debo usar PATCH o PUT en mi API REST?

274

Quiero diseñar mi punto final de descanso con el método apropiado para el siguiente escenario.

Hay un grupo Cada grupo tiene un estado. El grupo puede ser activado o desactivado por el administrador.

¿Debo diseñar mi punto final como

PUT /groups/api/v1/groups/{group id}/status/activate

O

PATCH /groups/api/v1/groups/{group id}

with request body like 
{action:activate|deactivate}
java_geek
fuente
1
Ambos estan bien. Pero eche un vistazo al RFC para el formato JSON PATCH ( tools.ietf.org/html/rfc6902 ). PATCH espera obtener algún tipo de documento diff / patch para la carga útil (y JSON sin formato no es uno de ellos).
Jørn Wildt
1
@ JørnWildt no, PUT sería una elección horrible. ¿Qué estás poniendo ahí? PATCH es la única opción sensata. Bueno, en este caso, podría usar el formato PATCH presentado en la pregunta, y simplemente usar el método PUT; El ejemplo PUT es simplemente incorrecto.
thecoshman
3
No hay nada de malo en exponer una o más propiedades como recursos independientes que un cliente puede OBTENER y modificar con PUT. Pero sí, la URL debería ser / groups / api / v1 / groups / {group id} / status al que puede PONER "activo" o "inactivo" o GET para leer el estado actual.
Jørn Wildt
3
Aquí hay una buena explicación de cómo PATCH realmente debería usarse: williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot
rishat
44
" activate" no es una construcción RESTful adecuada. Probablemente esté intentando actualizar statusa "activo" o "desactivado". en cuyo caso puede PARCHEAR .../statuscon la cadena "activa" o "desactivada" en el cuerpo. O si está intentando actualizar un booleano en status.active, puede PARCHEAR .../status/activecon el booleano en el cuerpo
Augie Gardner

Respuestas:

328

El PATCHmétodo es la opción correcta aquí, ya que está actualizando un recurso existente: el ID del grupo. PUTsolo debe usarse si está reemplazando un recurso en su totalidad.

Más información sobre la modificación parcial de recursos está disponible en RFC 5789 . Específicamente, el PUTmétodo se describe de la siguiente manera:

Varias aplicaciones que extienden el Protocolo de transferencia de hipertexto (HTTP) requieren una función para realizar una modificación parcial de los recursos. El método HTTP PUT existente solo permite un reemplazo completo de un documento. Esta propuesta agrega un nuevo método HTTP, PATCH, para modificar un recurso HTTP existente.

Luke Peterson
fuente
1
Para ser justos, podría PONER la cadena 'activar' o 'desactivar' al recurso. Como (parece) que solo hay una cosa para alternar, reemplazarlo por completo no es un gran problema. Y permite una solicitud (insignificantemente) más pequeña.
thecoshman
35
Es importante tener en cuenta que RFC 5789 todavía está en fase de propuesta y no ha sido aceptado oficialmente y actualmente está marcado como 'irrata exist'. Esta 'mejor práctica' es muy debatida y técnicamente PATCH aún no es parte del estándar HTTP.
fishpen0
44
Solo mis 2 centavos unos años más tarde: podría considerar el estado en sí mismo como un recurso, y si es así, usar PUT contra / status técnicamente reemplazaría el recurso de estado en ese punto final.
Jono Stewart el
3
Me atrevería a argumentar en contra de los documentos, a pesar de que es "el" RFC. Los documentos indican que debe usar PATCH para modificar solo una parte de un recurso, pero omitió lo importante de que el método PATCH se defina como un método no idempotente. ¿Por qué? Si el método PUT se creó con la actualización / reemplazo de todo el recurso en mente, entonces ¿por qué no se creó el método PATCH como un método idempotente como PUT, si su propósito era simplemente actualizar la parte de un recurso? Para mí, parece más una diferencia en la idempotencia de la actualización, como "a = 5" (PUT) y "a = a + 5" (PATCH). Ambos pueden actualizar todo el recurso.
Mladen B.
179

La R en REST significa recurso

(Lo cual no es cierto, porque significa Representacional, pero es un buen truco para recordar la importancia de los Recursos en REST).

Acerca de PUT /groups/api/v1/groups/{group id}/status/activate: estás no actualizando una "activar". Un "activar" no es una cosa, es un verbo. Los verbos nunca son buenos recursos. Una regla general: si la acción, un verbo, está en la URL, probablemente no sea RESTful .

¿Qué haces en su lugar? O bien está "agregando", "eliminando" o "actualizando" una activación en un Grupo, o si lo prefiere: manipulando un recurso de "estado" en un Grupo. Personalmente, usaría "activaciones" porque son menos ambiguas que el concepto "estado": crear un estado es ambiguo, crear una activación no lo es.

  • POST /groups/{group id}/activation Crea (o solicita la creación de) una activación.
  • PATCH /groups/{group id}/activationActualiza algunos detalles de una activación existente. Como un grupo tiene solo una activación, sabemos a qué recurso de activación nos referimos.
  • PUT /groups/{group id}/activationInserta o reemplaza la activación anterior. Como un grupo tiene solo una activación, sabemos a qué recurso de activación nos referimos.
  • DELETE /groups/{group id}/activation Cancelará o eliminará la activación.

Este patrón es útil cuando la "activación" de un Grupo tiene efectos secundarios, como los pagos realizados, los correos enviados, etc. Solo POST y PATCH pueden tener tales efectos secundarios. Cuando, por ejemplo, una eliminación de una activación necesita, por ejemplo, notificar a los usuarios por correo, ELIMINAR no es la opción correcta; en ese caso, es probable que desee crear un recurso de desactivación : POST /groups/{group_id}/deactivation.

Es una buena idea seguir estas pautas, porque este contrato estándar lo deja muy claro para sus clientes, y todos los poderes y capas entre el cliente y usted saben cuándo es seguro volver a intentarlo y cuándo no. Digamos que el cliente está en algún lugar con wifi débil, y su usuario hace clic en "desactivar", lo que desencadena un DELETE: Si eso falla, el cliente puede simplemente volver a intentarlo, hasta que obtenga un 404, 200 o cualquier otra cosa que pueda manejar. Pero si activa un POST to deactivation, sabe que no debe volver a intentarlo: la POST implica esto.
Cualquier cliente ahora tiene un contrato que, cuando se sigue, protegerá contra el envío de 42 correos electrónicos "su grupo ha sido desactivado", simplemente porque su biblioteca HTTP seguía reintentando la llamada al backend.

Actualización de un solo atributo: use PATCH

PATCH /groups/{group id}

En caso de que desee actualizar un atributo. Por ejemplo, el "estado" podría ser un atributo en los Grupos que se pueden configurar. Un atributo como "estado" suele ser un buen candidato para limitarse a una lista blanca de valores. Los ejemplos usan algunos esquemas JSON indefinidos:

PATCH /groups/{group id} { "attributes": { "status": "active" } }
response: 200 OK

PATCH /groups/{group id} { "attributes": { "status": "deleted" } }
response: 406 Not Acceptable

Reemplazar el recurso, sin efectos secundarios, utiliza PUT.

PUT /groups/{group id}

En caso de que desee reemplazar un grupo completo. Esto no significa necesariamente que el servidor realmente cree un nuevo grupo y descarte el anterior, por ejemplo, los identificadores pueden seguir siendo los mismos. Pero para los clientes, esto es lo que PUT puede significar: el cliente debe asumir que obtiene un elemento completamente nuevo, basado en la respuesta del servidor.

El cliente, en caso de una PUTsolicitud, siempre debe enviar todo el recurso, con todos los datos necesarios para crear un nuevo elemento: por lo general, los mismos datos que requeriría una creación POST.

PUT /groups/{group id} { "attributes": { "status": "active" } }
response: 406 Not Acceptable

PUT /groups/{group id} { "attributes": { "name": .... etc. "status": "active" } }
response: 201 Created or 200 OK, depending on whether we made a new one.

Un requisito muy importante es que PUTsea ​​idempotente: si necesita efectos secundarios al actualizar un Grupo (o cambiar una activación), debe usarlo PATCH. Entonces, cuando la actualización resulte, por ejemplo, en el envío de un correo, no lo use PUT.

Berkes
fuente
3
Esto fue muy informativo para mí. "Este patrón es útil cuando la 'activación' de un grupo tiene efectos secundarios" - ¿Cómo es que este patrón es útil, específicamente en lo que respecta a cuando las acciones tienen efectos secundarios, a diferencia de los criterios de valoración iniciales OP
Abdul
1
@Abdul, el patrón es útil por muchas razones, pero si tiene efectos secundarios, debe ser muy claro para el cliente qué efectos tiene una acción. Cuando, por ejemplo, una aplicación de iOS decide enviar toda la libreta de direcciones como "contactos", debe quedar extremadamente claro qué efectos secundarios tiene la creación, actualización, eliminación, etc. de un contacto. Para evitar el envío masivo de todos los contactos, por ejemplo.
Berkes
1
En RESTfull PUT también puede cambiar la identidad de las entidades, por ejemplo, la identificación de la clave principal, donde podría provocar que falle una solicitud paralela. (por ejemplo, actualizar toda la entidad necesita eliminar algunas filas y agregar otras nuevas, por lo tanto, crear nuevas entidades) Donde PATCH nunca debe poder hacer eso, permitiendo un número ilimitado de solicitudes PATCH sin afectar otras "aplicaciones"
Piotr Kula
1
Muy útil respuesta. ¡Gracias! También agregaría un comentario, al igual que en la respuesta de Luke, señalando que la diferencia entre PUT / PATCH no es solo la actualización completa / parcial, sino también la idempotencia que es diferente. Esto no fue un error, fue una decisión intencional y creo que no mucha gente toma esto en consideración al decidir el uso del método HTTP.
Mladen B.
1
Los servicios de @richremer, como los modelos, son abstracciones internas. Del mismo modo que es una abstracción deficiente requerir una relación 1-1 entre los modelos de puntos finales REST y ORM o incluso las tablas de la base de datos, es una abstracción deficiente exponer los Servicios. El exterior, su API, debe comunicar modelos de dominio. La forma en que los implemente internamente no es una preocupación para la API. Debería ser libre de pasar de un ActivationService a un flujo de activación basado en CQRS, sin tener que cambiar su API.
Berkes
12

Recomendaría usar PATCH, porque su recurso 'grupo' tiene muchas propiedades pero en este caso, está actualizando solo el campo de activación (modificación parcial)

según el RFC5789 ( https://tools.ietf.org/html/rfc5789 )

El método HTTP PUT existente solo permite un reemplazo completo de un documento. Esta propuesta agrega un nuevo método HTTP, PATCH, para modificar un recurso HTTP existente.

Además, en más detalles,

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, nuevos recursos
puede crearse o modificarse los existentes mediante la aplicación de un
PATCH.

PATCH no es seguro ni idempotente como se define en [RFC2616], Sección 9.1.

Los clientes deben elegir cuándo usar PATCH en lugar de PUT. Por
ejemplo, si el tamaño del documento de parche es mayor que el tamaño de los
nuevos datos de recursos que se usarían en un PUT, entonces podría tener
sentido usar PUT en lugar de PATCH. Una comparación con POST es aún más difícil, porque POST se usa de formas muy variadas y puede
abarcar operaciones similares a PUT y PATCH si el servidor lo elige. Si
la operación no modifica el recurso identificado por el Request-URI de una manera predecible, se debe considerar POST en lugar de PATCH
o PUT.

El código de respuesta para PATCH es

El código de respuesta 204 se usa porque la respuesta no lleva un cuerpo de mensaje (que tendría una respuesta con el código 200). Tenga en cuenta que también podrían usarse otros códigos de éxito.

también consulte thttp: //restcookbook.com/HTTP%20Methods/patch/

Advertencia: una API que implementa PATCH debe parchear atómicamente. NO DEBE ser posible que los recursos estén parcheados a medias cuando lo solicite un GET.

Clojurevangelist
fuente
7

Dado que desea diseñar una API utilizando el estilo arquitectónico REST, debe pensar en sus casos de uso para decidir qué conceptos son lo suficientemente importantes como para exponerlos como recursos. Si decide exponer el estado de un grupo como un recurso secundario, puede asignarle el siguiente URI e implementar soporte para los métodos GET y PUT:

/groups/api/groups/{group id}/status

La desventaja de este enfoque sobre PATCH para la modificación es que no podrá realizar cambios a más de una propiedad de un grupo atómica y transaccionalmente. Si los cambios transaccionales son importantes, use PATCH.

Si decide exponer el estado como un recurso secundario de un grupo, debería ser un enlace en la representación del grupo. Por ejemplo, si el agente obtiene el grupo 123 y acepta XML, el cuerpo de la respuesta podría contener:

<group id="123">
  <status>Active</status>
  <link rel="/linkrels/groups/status" uri="/groups/api/groups/123/status"/>
  ...
</group>

Se necesita un hipervínculo para cumplir con el hipermedia como el motor de la condición de estado de aplicación del estilo arquitectónico REST.

Andrew Dobrowolski
fuente
0

Generalmente preferiría algo un poco más simple, como activate/ deactivatesub-resource (vinculado por un Linkencabezado con rel=service).

POST /groups/api/v1/groups/{group id}/activate

o

POST /groups/api/v1/groups/{group id}/deactivate

Para el consumidor, esta interfaz es muy simple, y sigue los principios REST sin atascarse en la conceptualización de "activaciones" como recursos individuales.

rico remer
fuente
0

Una posible opción para implementar tal comportamiento es

PUT /groups/api/v1/groups/{group id}/status
{
    "Status":"Activated"
}

Y obviamente, si alguien necesita desactivarlo, PUTtendrá Deactivatedestado en JSON.

En caso de necesidad de activación / desactivación masiva, PATCHpuede ingresar al juego (no para el grupo exacto, sino para el groupsrecurso:

PATCH /groups/api/v1/groups
{
    { “op”: “replace”, “path”: “/group1/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group7/status”, “value”: “Activated” },
    { “op”: “replace”, “path”: “/group9/status”, “value”: “Deactivated” }
}

En general, esta es una idea como sugiere @Andrew Dobrowolski, pero con ligeros cambios en la realización exacta.

Ivan Sokalskiy
fuente