API RESTful. ¿Debo devolver el objeto que se creó / actualizó?

36

Estoy diseñando un servicio web RESTful usando WebApi y me preguntaba qué respuestas HTTP y cuerpos de respuesta devolver al actualizar / crear objetos.

Por ejemplo, puedo usar el método POST para enviar algunos JSON al servicio web y luego crear un objeto. ¿Es una buena práctica establecer el estado HTTP en creado (201) o bien (200) y simplemente devolver un mensaje como "Nuevo empleado agregado", o devolver el objeto que se envió originalmente?

Lo mismo ocurre con el método PUT. ¿Qué estado HTTP es mejor usar y debo devolver el objeto que se creó o simplemente un mensaje? Teniendo en cuenta el hecho de que el usuario sabe qué objeto está tratando de crear / actualizar de todos modos.

¿Alguna idea?

Ejemplo:

Añadir nuevo empleado:

POST /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Name" : "Joe Bloggs",
        "Department" : "Finance"
    }
}

Actualizar empleado existente:

PUT /api/employee HTTP/1.1
Host: localhost:8000
Content-Type: application/json

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Respuestas:

Respuesta con objeto creado / actualizado

HTTP/1.1 201 Created
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Employee": {
        "Id" : 1
        "Name" : "Joe Bloggs",
        "Department" : "IT"
    }
}

Respuesta con solo mensaje:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json; charset=utf-8
Date: Mon, 28 Mar 2016 14:32:39 GMT

{
    "Message": "Employee updated"
}

Respuesta con solo código de estado:

HTTP/1.1 204 No Content
Content-Length: 39
Date: Mon, 28 Mar 2016 14:32:39 GMT
iswinky
fuente
2
Buena pregunta, pero usar el término "mejores prácticas" es una especie de tabú en este sitio meta.programmers.stackexchange.com/questions/2442/… Es posible que solo desee reformular la pregunta. meta.programmers.stackexchange.com/questions/6967/…
Snoop
3
Como un poco de seguimiento, ¿sería una buena idea tener un indicador en la solicitud para que (por ejemplo) una aplicación móvil pueda recuperar todo el objeto cuando está en WiFi, pero solo la ID cuando se usan datos móviles? ¿Hay un encabezado que deba usarse para evitar contaminar el JSON?
Andrew dice Reinstate Monica
@AndrewPiliser Idea interesante, aunque personalmente creo que es mejor elegir un enfoque y seguirlo. Luego, a medida que su aplicación se desarrolle o se vuelva más popular, optimícela
iswinky
@AndrewPiliser su idea es muy similar a la UPDATE/INSERT ... RETURNINGvariante Postgresql para SQL. Es extremadamente útil, especialmente porque mantiene la presentación de nuevos datos y la solicitud de la versión actualizada atómica.
beldaz

Respuestas:

31

Como con la mayoría de las cosas, depende. Su compensación es la facilidad de uso frente al tamaño de la red. Puede ser muy útil para los clientes ver el recurso creado. Puede incluir campos poblados por el servidor, como el último tiempo de creación. Como parece que está incluyendo el en idlugar de usar hateoas, los clientes probablemente deseen ver la identificación del recurso que acaban de POSTeditar.

Si no incluye el recurso creado, no cree un mensaje arbitrario. Los campos 2xx y Ubicación son información suficiente para que los clientes estén seguros de que su solicitud se manejó adecuadamente.

Eric Stein
fuente
+1 El objetivo de hateoas de no dejar que el cliente componga la uri también se puede lograr permitiendo que el cliente complete las plantillas de URL proporcionadas por el servidor con identificaciones específicas. Sí, el cliente "compone" pero solo en una forma de "llenar los espacios en blanco". Si bien no es un HATEOAS puro, logra el objetivo y hace que trabajar con objetos que tienen una (gran) cantidad de uri de "acción" sea un poco menos sensible al ancho de banda, sin mencionar cuando coloca esos objetos en una lista (grande).
Marjan Venema
3
+1 en el consejo "por favor no cree un mensaje arbitrario"
HairOfTheDog
¿El "mensaje no arbitrario" se centra en los mensajes de cadena o en cualquier valor de retorno que no sea el recurso creado? Me estoy centrando en los casos en los que devolvemos la identificación del recurso creado (pero no el recurso en sí) y me preguntaba dónde encaja esto.
Flater
12

Personalmente, siempre vuelvo solo 200 OK.

Para citar tu pregunta

Teniendo en cuenta el hecho de que el usuario sabe qué objeto está tratando de crear / actualizar de todos modos.

¿Por qué agregar ancho de banda adicional (que podría tener que pagarse) para decirle al cliente lo que ya sabe?

Mawg
fuente
1
Eso es lo que estaba pensando, si no es válido, puede devolver mensajes de validación, pero si es válido y se crea / actualiza, verifique el código de estado HTTP y muestre al usuario un mensaje, por ejemplo, "Hooray" basado en eso
curioso el
3
Consulte stackoverflow.com/a/827045/290182 con respecto a 200/ 204 No Contentpara evitar confundir jQuery y similares.
beldaz
10

@iswinky Siempre enviaría la carga útil en caso de POST y PUT.

En el caso de POST, puede crear la entidad con una ID interna o un UUID. Por lo tanto, tiene sentido devolver la carga útil.

De manera similar, en el caso de PUT, podría ignorar algunos campos del usuario (valores inmutables, por ejemplo), o en el caso de un PATCH, los datos también podrían haber sido modificados por otros usuarios.

Enviar los datos nuevamente tal como fueron almacenados siempre es una buena idea y definitivamente no hace daño. Si la persona que llama no necesita estos datos devueltos, no los procesará, sino que solo consumirá el código de estado. De lo contrario, pueden usar estos datos como algo para actualizar la interfaz de usuario.

Solo en el caso de DELETE, no enviaría la carga útil y haría un 200 con un contenido de respuesta o un 204 sin contenido de respuesta.

Editar: Gracias a algunos comentarios de abajo, estoy reescribiendo mi respuesta. Todavía mantengo la forma en que diseño mis API y envío las respuestas, pero creo que tiene sentido calificar algunos de mis pensamientos de diseño.

a) Cuando digo devolver la carga útil, en realidad quise decir devolver los datos del recurso, no la misma carga que vino en la solicitud. Por ejemplo: si envía una carga útil de creación, en el backend puedo crear otras entidades como UUID y (tal vez) marcas de tiempo e incluso algunas conexiones (gráficas). Enviaría todo esto de vuelta en la respuesta (no solo la carga útil de la solicitud como está, lo cual no tiene sentido).

b) No enviaría respuestas en caso de que la carga útil sea muy grande. He discutido esto en los comentarios, pero me gustaría advertir que haré todo lo posible para diseñar mis API o mis recursos de manera que no tenga que tener cargas útiles muy grandes. Intentaría dividir los recursos en entidades más pequeñas y manejables de modo que cada recurso esté definido por 15-20 atributos JSON y no más grandes.

En el caso de que el objeto sea muy grande o el objeto principal se actualice, entonces enviaría las estructuras anidadas como hrefs.

En pocas palabras, definitivamente trataría de devolver los datos que tengan sentido para que el consumidor / UI procese de inmediato y se haga con una acción de API atómica en lugar de tener que ir y buscar 2-5 API más solo para actualizar la UI publicar el creación / actualización de los datos.

Las API de servidor a servidor pueden pensar de manera diferente al respecto. Me estoy centrando en las API que impulsan la experiencia del usuario.

ksprashu
fuente
Puedo ver muchas situaciones en las que devolver la carga útil completa es una mala idea, cuando la carga útil es grande.
beldaz
2
@beldaz está completamente de acuerdo. YMMV basado en el diseño de la API REST. En general, evito los objetos muy grandes y los descompongo en una serie de recursos secundarios / PUT. Si la carga útil es muy grande, hay mejores maneras de hacer esto, y allí querrás hacer HATEOAS (como dice Marjan arriba) donde devuelves la referencia al objeto en lugar del objeto en sí.
ksprashu
@ksprashu: "Por lo tanto, tiene sentido devolver la carga útil". Creo que esta es una mala idea, porque así se puede obtener un recurso de muchas maneras: a través de GET, como respuesta de POST, como respuesta de PUT. Significa que el cliente obtiene 3 recursos con una estructura potencialmente diferente . Donde, como si solo devolviera URI (ubicación), sin cuerpo, la única forma de obtener un recurso sería GET. Esto aseguraría que el cliente siempre obtenga respuestas consistentes.
Mentallurg
@mentallurg Me doy cuenta de que puede que no haya redactado este derecho. Gracias por mencionarlo. He editado mi respuesta. Avísame si esto tiene más sentido.
ksprashu
Mientras implemente un servicio para su trabajo a domicilio, realmente no importa. Hazlo como quieras. Ahorra tu tiempo.
Mentallurg
9

Haciendo referencia a los estándares RFC de enlace , debe devolver el estado 201 (creado) al almacenar correctamente el recurso de solicitud mediante Post. En la mayoría de las aplicaciones, el servidor mismo genera la identificación del recurso, por lo que es una buena práctica devolver la identificación del recurso creado. Devolver todo el objeto es la sobrecarga para la solicitud de publicación. La práctica ideal es devolver la ubicación de la URL del recurso recién creado.

Por ejemplo, puede consultar el siguiente ejemplo que guarda el objeto de empleado en la base de datos y devuelve la URL del objeto de recurso recién creado como respuesta.

@RequestMapping(path = "/employees", method = RequestMethod.POST)
public ResponseEntity<Object> saveEmployee(@RequestBody Employee employee) {
        int id = employeeService.saveEmployee(employee);
        URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(id).toUri();
        return ResponseEntity.created(uri).build();
}

Este punto final de descanso devolverá la respuesta como:

Estado 201 - CREADO

Ubicación del encabezado → http: // localhost: 8080 / employee / 1

Shubham Bondarde
fuente
Agradable y limpio - voy a seguir esto desde ahora y más allá
Hassan Tareq
0

Haría una carga útil en el cuerpo de retorno condicional a un parámetro HTTP.

En la mayoría de los casos, lo mejor es devolver algún tipo de contenido al consumidor de API para evitar viajes de ida y vuelta innecesarios (una de las razones por las que existe GraphQL).

De hecho, a medida que nuestras aplicaciones se vuelven más intensivas en datos y distribuidas, trato de observar esta guía:

Mi directriz :

Cada vez que hay un caso de uso que exige un OBTENER inmediatamente después de una POST o PUT, ese es un caso en el que sería mejor simplemente devolver algo en la respuesta POST / PUT.

Cómo se hace esto y qué tipo de contenido devolver de un PUT o POST es específico de la aplicación. Ahora, sería interesante si la aplicación pudiera parametrizar el tipo de "contenido" en el cuerpo de la respuesta (¿queremos solo la ubicación del nuevo objeto, o algunos de los campos, o el objeto completo, etc.)

Una aplicación podría definir un conjunto de parámetros que un POST / PUT puede recibir para controlar el tipo de "contenido" que se devolverá en el cuerpo de la respuesta. O podría codificar algún tipo de consulta GraphQL como parámetro (puedo ver que esto es útil pero también se convierte en una pesadilla de mantenimiento).

De cualquier manera, me parece que:

  1. Está bien (y probablemente sea deseable) devolver algo en un cuerpo de respuesta POST / PUT.
  2. Cómo se hace esto es específico de la aplicación y casi imposible de generalizar.
  3. No desea devolver el "contexto" de gran tamaño de forma predeterminada (ruido de tráfico que anula toda la razón de alejarse de POST seguido de GET).

Entonces, 1) hazlo, pero 2) hazlo simple.

Otra opción que he visto es que las personas crean puntos finales alternativos (por ejemplo, / clients para POST / PUT que no devuelven nada en el cuerpo y / customer_with_details para POST / PUT a / clients, pero que devuelven algo en el cuerpo de respuesta).

Sin embargo, evitaría este enfoque. ¿Qué sucede cuando legítimamente necesitas devolver diferentes tipos de contenido? ¿Un punto final por tipo de contenido? No escalable ni mantenible.

luis.espinal
fuente