REST: ¿poner ID en el cuerpo o no?

96

Digamos que quiero tener un recurso RESTful para personas, donde el cliente pueda asignar ID.

Una persona se ve así: {"id": <UUID>, "name": "Jimmy"}

Ahora bien, ¿cómo debería guardarlo (o "PONER") el cliente?

  1. PUT /person/UUID {"id": <UUID>, "name": "Jimmy"} - ahora tenemos esta desagradable duplicación que tenemos que verificar todo el tiempo: ¿La identificación en el cuerpo coincide con la de la ruta?
  2. Representación asimétrica:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID devoluciones {"id": <UUID>, "name": "Jimmy"}
  3. No hay ID en el cuerpo - ID solo en la ubicación:
    • PUT /person/UUID {"name": "Jimmy"}
    • GET /person/UUID devoluciones {"name": "Jimmy"}
  4. No POSTparece una buena idea, ya que el cliente genera la identificación.

¿Cuáles son los patrones comunes y las formas de resolverlo? Las identificaciones solo en la ubicación parece la forma más dogmáticamente correcta, pero también dificulta la implementación práctica.

Konrad Garus
fuente

Respuestas:

62

No hay nada de malo en tener diferentes modelos de lectura / escritura: el cliente puede escribir una representación de recurso donde, después, el servidor puede devolver otra representación con elementos agregados / calculados en ella (o incluso una representación completamente diferente; no hay nada en ninguna especificación en contra de eso , el único requisito es que PUT cree o reemplace el recurso).

Entonces optaría por la solución asimétrica en (2) y evitaría la "desagradable verificación de duplicación" en el lado del servidor al escribir:

PUT /person/UUID {"name": "Jimmy"}

GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}
Jørn Wildt
fuente
2
Y si aplica la escritura (estática o dinámica), no puede tener modelos sin ID sin dolor ... Por lo tanto, es mucho más fácil eliminar la ID de la URL para las solicitudes PUT. No será "relajante" pero será correcto.
Ivan Kleshnin
2
Mantenga TO adicional sin idjunto con TO con id y entidad y convertidores adicionales y una sobrecarga demasiado grande para los programadores.
Grigory Kislin
¿Qué sucede si obtengo el ID de BODY ej .: PUT / person {"id": 1, "name": "Jimmy"}? ¿Sería una mala práctica?
Bruno Santos
Poner la identificación en el cuerpo estaría bien. Utilice un GUID para ID en lugar de un número entero; de lo contrario, corre el riesgo de tener ID duplicados.
Jørn Wildt
Esto está mal. Mira mi respuesta. PUT debe contener todo el recurso. Use PATCH si desea excluir la identificación y actualizar solo partes del registro.
CompEng88
27

Si es una API pública, debe ser conservador al responder, pero acepte libremente.

Con eso quiero decir, debes apoyar tanto 1 como 2. Estoy de acuerdo en que 3 no tiene sentido.

La forma de admitir tanto 1 como 2 es obtener la identificación de la URL si no se proporciona ninguna en el cuerpo de la solicitud, y si está en el cuerpo de la solicitud, validar que coincida con la identificación en la URL. Si los dos no coinciden, devuelva una respuesta 400 Bad Request.

Cuando devuelva un recurso de persona sea conservador y siempre incluya el id en el json, aunque sea opcional en el put.

Jay Pete
fuente
3
Esta debería ser la solución aceptada. Una API siempre debe ser fácil de usar. Debe ser opcional en el cuerpo. No debería recibir una identificación de un POST y luego tener que hacerla indefinida en un PUT. Además, el punto de respuesta de 400 hecho es correcto.
Michael
Aproximadamente 400 códigos, consulte también softwareengineering.stackexchange.com/questions/329229/… . En resumen, un código 400 no es inapropiado, solo menos específico, en comparación con 422.
Grigory Kislin
8

Una solución a este problema implica el concepto algo confuso de "Hipertexto como motor del estado de la aplicación" o "HATEOAS". Esto significa que una respuesta REST contiene los recursos o acciones disponibles que se realizarán como hipervínculos. Con este método, que formaba parte de la concepción original de REST, los identificadores / ID únicos de los recursos son en sí mismos hipervínculos. Entonces, por ejemplo, podría tener algo como:

GET /person/<UUID> {"person": {"location": "/person/<UUID>", "data": { "name": "Jimmy"}}}

Luego, si desea actualizar ese recurso, puede hacer (pseudocódigo):

updatedPerson = person.data
updatedPerson.name = "Timmy"
PUT(URI: response.resource, data: updatedPerson)

Una ventaja de esto es que el cliente no tiene que tener idea sobre la representación interna del servidor de los ID de usuario. Las ID pueden cambiar, e incluso las URL mismas podrían cambiar, siempre que el cliente tenga una forma de descubrirlas. Por ejemplo, al obtener una colección de personas, puede devolver una respuesta como esta:

GET /people
{ "people": [
    "/person/1",
    "/person/2"
  ]
}

(Por supuesto, también puede devolver el objeto de persona completo para cada persona, según las necesidades de la aplicación).

Con este método, piensa en sus objetos más en términos de recursos y ubicaciones, y menos en términos de identificación. Por tanto, la representación interna del identificador único se desacopla de la lógica de su cliente. Este fue el ímpetu original detrás de REST: crear arquitecturas cliente-servidor que estén más débilmente acopladas que los sistemas RPC que existían antes, utilizando las características de HTTP. Para obtener más información sobre HATEOAS, consulte el artículo de Wikipedia y este breve artículo .

bthecohen
fuente
4

En una inserción no es necesario agregar la identificación en la URL. De esta manera, si envía una ID en un PUT, puede interpretarlo como una ACTUALIZACIÓN para cambiar la clave principal.

  1. INSERTAR:

    PUT /persons/ 
      {"id": 1, "name": "Jimmy"}
    HTTP/1.1 201 Created     
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}
    
    GET /persons/1
    
    HTTP/1.1 200 OK
      {"id": 1, "name": "Jimmy", "other_field"="filled_by_server"}  
    
  2. ACTUALIZAR

    PUT /persons/1 
         {"id": "2", "name": "Jimmy Jr"} - 
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="filled_by_server"}
    
    GET /persons/2 
    
    HTTP/1.1 200 OK
         {"id": "2", "name": "Jimmy Jr", "other_field"="updated_by_server"}
    

La API JSON utiliza este estándar y resuelve algunos problemas al devolver el objeto insertado o actualizado con un enlace al nuevo objeto. Algunas actualizaciones o inserciones pueden incluir alguna lógica empresarial que cambiará campos adicionales

También verá que puede evitar obtener después de insertar y actualizar.

borjab
fuente
3

Esto se ha preguntado antes; vale la pena echarle un vistazo a la discusión:

¿Debería una respuesta RESTful GET devolver el ID de un recurso?

Ésta es una de esas preguntas en las que es fácil empantanarse en un debate sobre qué es y qué no es "DESCANSO" .

Por lo que vale, trato de pensar en términos de recursos consistentes y no cambiar el diseño de los mismos entre métodos. Sin embargo, en mi humilde opinión, lo más importante desde la perspectiva de la usabilidad es que usted sea consistente en toda la API.

Ben Morris
fuente
2

Si bien está bien tener diferentes representaciones para diferentes operaciones, una recomendación general para PUT es contener la carga útil ENTERA . Eso significa queid debería estar allí también. De lo contrario, debería utilizar PATCH.

Habiendo dicho eso, creo que PUT debería utilizarse principalmente para actualizaciones y idsiempre debería pasarse también en la URL. Como resultado de eso, usar PUT para actualizar el identificador de recursos es una mala idea. Nos deja en una situación indeseable cuando iden la URL puede ser diferente a iden el cuerpo.

Entonces, ¿cómo resolvemos tal conflicto? Básicamente tenemos 2 opciones:

  • lanzar una excepción 4XX
  • agregue un encabezado Warning( X-API-Warnetc.).

Eso es lo más cerca que puedo estar de responder esta pregunta porque el tema en general es una cuestión de opinión.

yuranos
fuente
2

Para su información, las respuestas aquí son incorrectas.

Ver:

https://restfulapi.net/rest-api-design-tutorial-with-example/

https://restfulapi.net/rest-put-vs-post/

https://restfulapi.net/http-methods/#patch

PONER

Utilice las API PUT principalmente para actualizar el recurso existente (si el recurso no existe, entonces la API puede decidir crear un nuevo recurso o no). Si la API PUT ha creado un nuevo recurso, el servidor de origen DEBE informar al agente de usuario a través del código de respuesta HTTP 201 (Creado) y si se modifica un recurso existente, el 200 (OK) o el 204 (Sin contenido) DEBERÍAN enviarse códigos de respuesta para indicar la finalización satisfactoria de la solicitud.

Si la solicitud pasa a través de un caché y el Request-URI identifica una o más entidades actualmente en caché, esas entradas DEBERÍAN tratarse como obsoletas. Las respuestas a este método no se pueden almacenar en caché.

Utilice PUT cuando desee modificar un recurso singular que ya forma parte de la colección de recursos. PUT reemplaza el recurso en su totalidad. Utilice PATCH si la solicitud actualiza parte del recurso.

PARCHE

Las solicitudes HTTP PATCH son para realizar una actualización parcial en un recurso. Si ve que las solicitudes PUT también modifican una entidad de recurso para que quede más claro: el método PATCH es la opción correcta para actualizar parcialmente un recurso existente y PUT solo debe usarse si está reemplazando un recurso en su totalidad.

Entonces deberías usarlo de esta manera:

POST    /device-management/devices      : Create a new device
PUT     /device-management/devices/{id} : Update the device information identified by "id"
PATCH   /device-management/devices/{id} : Partial-update the device information identified by "id"

Las prácticas RESTful indican que no debería importar lo que PONGA en / {id}; el contenido del registro debe actualizarse al que proporciona la carga útil, pero GET / {id} aún debe vincularse al mismo recurso.

En otras palabras, PUT / 3 puede actualizar a la identificación de la carga útil a 4, pero GET / 3 aún debería vincularse a la misma carga útil (y devolver la que tiene la identificación establecida en 4).

Si está decidiendo que su API requiere el mismo identificador en el URI y la carga útil, es su trabajo asegurarse de que coincida, pero definitivamente use PATCH en lugar de PUT si está excluyendo la identificación en la carga útil que debería estar allí en su totalidad . Aquí es donde la respuesta aceptada se equivocó. PUT debe reemplazar todo el recurso, mientras que el parche puede ser parcial.

CompEng88
fuente
1

No hay nada malo en utilizar diferentes enfoques. pero creo que la mejor manera es la solución con 2nd .

 PUT /person/UUID {"name": "Jimmy"}

 GET /person/UUID returns {"id": <UUID>, "name": "Jimmy"}

se usa principalmente de esta manera, incluso el marco de la entidad usa esta técnica cuando la entidad se agrega en dbContext, la clase sin el ID generado es el ID generado por referencia en Entity Framework.

Shan Khan
fuente
1

Estoy viendo esto desde un punto de vista JSON-LD / Web semántica porque es un buen camino a seguir para lograr una conformidad REST real como he descrito en estas diapositivas . Mirándolo desde esa perspectiva, no hay duda de optar por la opción (1.) ya que el ID (IRI) de un recurso web siempre debe ser igual a la URL que puedo usar para buscar / eliminar la referencia del recurso. Creo que la verificación no es realmente difícil de implementar ni computacionalmente intensiva; así que no considero que esta sea una razón válida para optar por la opción (2.). Creo que la opción (3.) no es realmente una opción ya que POST (crear nuevo) tiene una semántica diferente a PUT (actualizar / reemplazar).

vanthome
fuente
0

Es posible que deba examinar los tipos de solicitud PATCH / PUT.

Las solicitudes PATCH se utilizan para actualizar un recurso parcialmente, mientras que en las solicitudes PUT, debe enviar todo el recurso donde se anula en el servidor.

En lo que respecta a tener un ID en la URL, creo que siempre debe tenerlo, ya que es una práctica estándar identificar un recurso. Incluso la API de Stripe funciona de esa manera.

Puede utilizar una solicitud PATCH para actualizar un recurso en el servidor con ID para identificarlo, pero no actualice el ID real.

Noman Ur Rehman
fuente