Operaciones no CRUD en un servicio RESTful

106

¿Cuál es la forma "RESTful" de agregar operaciones que no son CRUD a un servicio RESTful? Digamos que tengo un servicio que permite el acceso CRUD a registros como este:

GET /api/car/123           <- Returns information for the Car object with ID 123
POST /api/car              <- Creates a new car (with properties in the request)
PUT /api/car/123           <- Updates car 123 (with properties in the request)
DELETE /api/car/123        <- Deletes car 123    
POST /api/car/123/wheel/   <- Creates a wheel and associates it to car 123

Si quiero cambiar el color del automóvil, simplemente POST /api/car/123incluiría una variable POST para el nuevo color.

Pero digamos que quiero comprar un automóvil, y esa operación es más complicada que simplemente actualizar la propiedad del "automóvil de propiedad" de un registro de "usuario". ¿Es RESTful simplemente hacer algo como POST /api/car/123/purchase, donde "comprar" es esencialmente un nombre de método? ¿O debería usar un verbo HTTP personalizado, como en PURCHASElugar de POST?

¿O las operaciones que no son de CRUD están completamente fuera del alcance de REST?

MikeWyatt
fuente
5
Si está cambiando el color de un automóvil, sería mejor usar PATCH /api/car/123y enviar un parámetro de color O usar PUT /api/car/123y enviar todo el objeto del automóvil. POST inferiría que está creando un automóvil nuevo y probablemente nunca debería incluir una identificación al final de la URL
RonnyKnoxville

Respuestas:

65

Piense en la compra como una entidad comercial o un recurso en el diccionario RESTful. Dicho esto, realizar una compra en realidad es crear un nuevo recurso. Entonces:

POST /api/purchase

hará un nuevo pedido. Los detalles (usuario, automóvil, etc.) deben estar referenciados por id (o URI) dentro del contenido enviado a esta dirección.

No importa que pedir un automóvil no sea solo una simple INSERCIÓN en la base de datos. En realidad, REST no se trata de exponer las tablas de su base de datos como operaciones CRUD. Desde el punto de vista lógico, está creando un pedido (compra), pero el lado del servidor es libre de realizar tantos pasos de procesamiento como desee.

Incluso puede abusar aún más del protocolo HTTP. Use el Locationencabezado para devolver un enlace al pedido recién creado, elija cuidadosamente los códigos de respuesta HTTP para informar a los usuarios sobre problemas (del lado del servidor o del cliente), etc.

Tomasz Nurkiewicz
fuente
3
REST se trata de manipular el estado de los recursos y cada operación comercial debe asignarse a las operaciones CRUD del estado. Si necesita una semántica de operaciones comerciales estrictas, tendrá que seguir el camino SOAP (SOAP es en realidad el paso de mensajes, pero generalmente se organiza en operaciones de solicitud-respuesta).
Tomasz Nurkiewicz
23
El diseño de "compra como recurso" parece impecable. ¿Qué pasa si el recurso es una "cerveza" ... y quiero que el servidor la beba ... (si fuera por mí, seguramente lo CONSEGUIRÍA;)) ... deberíamos considerar la "acción de beber" como un recurso ?! .. o es "beber una cerveza", una dura operación comercial?! Más en serio, ¿el diseño RESTful se trata de considerar las acciones como recursos? ...
Myobis
2
¿Cómo expondría "aprobar orden de compra" a través de un servicio REST? Creo que @TomaszNurkiewicz tiene razón en que cualquier cosa que no se pueda hacer claramente de una manera CRUD necesitará la semántica de operación proporcionada por SOAP. A menos que la "aprobación de la orden de compra" sea un modelo / entidad en sí mismo. Por ejemplo, POST / po-aprobación (con detalles de la orden de compra en la solicitud).
mydoghasworms
2
Desde el punto de vista de un cliente REST, "aprobar la orden de compra" debería ser simplemente otra actualización de la orden. Por ejemplo, cambie "Aprobado" a "verdadero" y envíe la actualización al servidor. El servidor probablemente necesitará hacer un montón de comprobaciones y probablemente necesitará actualizar / crear un montón de otros recursos. Pero ese es el problema de los servidores y no debería ser visible para el cliente.
AVee
2
@antinome: "Suponga que el cliente sabe algo de esto", si ese es el caso, no está haciendo REST (¡aún podría ser un software válido y sensato!). REST fue diseñado para poder crear clientes que no conozcan ese tipo de cosas, para crear clientes que aún funcionen si cambia el comportamiento del servidor. Lo que está tratando de hacer es RPC clásico, debe revisar su enfoque para que se ajuste a REST o aceptar que está haciendo RPC y usar un protocolo destinado a RPC como SOAP. REST se esfuerza mucho por no ser RPC, por lo que nunca será una buena opción cuando desee / necesite RPC.
AVee
15

La forma RESTful, según tengo entendido, es que no necesita nuevos verbos HTTP, hay un sustantivo en alguna parte que significará lo que debe hacer.

¿Comprar un coche? Bueno, ¿no es eso?

POST /api/order
djna
fuente
2
¿No se usa PUT para actualizar recursos ya que es idempotente? Esto significa que puede llamarlo tantas veces como desee, pero solo la primera / última llamada es importante. POST, por otro lado, se usa para crear recursos y llamarlo dos veces debería crear dos.
Tomasz Nurkiewicz
1
@Tomas, sí, error tipográfico. Sin embargo, el principio es importante, estamos tratando con algo nuevo, un orden, sin necesidad de un nuevo verbo.
djna
5

Lo que realmente estás haciendo es crear un pedido. Por lo tanto, agregue otro recurso para ordenar y publicar y colocar allí durante el proceso de pedido.

Piense en términos de recursos en lugar de llamadas a métodos.

Para finalizar el pedido, probablemente POST / api / order // complete o algo similar.

Andrew Kothmann
fuente
3

Siento que las API REST ayudan de muchas más formas que solo proporcionar semántica. Por lo tanto, no se puede elegir el estilo RPC solo por algunas llamadas que parecen tener más sentido en el estilo de operación RPC. Un ejemplo es la API de Google Maps para encontrar direcciones entre dos lugares. Tiene este aspecto : http://maps.googleapis.com/maps/api/directions/json?origin=Jakkur&destination=Hebbal

Podrían haberlo llamado "findDirections" (verbo) y tratarlo como una operación. Más bien, hicieron "dirección" (sustantivo) como un recurso y trataron la búsqueda de direcciones como una consulta en el recurso de direcciones (aunque internamente no podría haber un recurso real llamado dirección y podría implementarse mediante la lógica empresarial para encontrar direcciones basadas en parámetros).

Maruthi
fuente
Ese es un mal ejemplo. En este caso, las direcciones (todas las direcciones posibles, un número infinito de ellas) es el recurso y los parámetros son solo filtros. Pero no puede hacer una "compra" con esto, ya que los filtros solo tienen sentido en operaciones de obtención y hacer un pedido o cancelarlo son operaciones que cambian los datos
Tseng
2
la compra sería POST a / pedido con un json en el cuerpo para indicar que se creó un pedido. cancel sería PUT to / order con un json que lleva el cambio de estado de la orden para indicar que se trata de una actualización idempotente. Todavía tengo que encontrarme con una operación que no se puede expresar en un formato de recurso. Así que estaría interesado en ver un ejemplo como ese
Maruthi