RESTFul: acciones de cambio de estado


60

Estoy planeando construir RESTfull API pero hay algunas preguntas arquitectónicas que están creando algunos problemas en mi cabeza. Me gustaría evitar agregar lógica de negocio de back-end a los clientes, ya que es difícil mantener la actualización de múltiples plataformas de clientes en tiempo real cuando la lógica de negocios puede cambiar rápidamente.

Digamos que tenemos un artículo como recurso (api / article), ¿cómo debemos implementar acciones como publicar, anular la publicación, activar o desactivar, etc., pero tratar de mantenerlo lo más simple posible?

1) ¿Deberíamos usar api / article / {id} / {action} ya que puede ocurrir una gran cantidad de lógica de back-end como empujar a ubicaciones remotas o cambiar múltiples propiedades. Probablemente lo más difícil aquí es que necesitamos enviar todos los datos del artículo a la API para su actualización y no se pudo implementar el trabajo multiusuario. Por ejemplo, el editor podría enviar datos 5 segundos más antiguos y sobrescribir la corrección que otro periodista acaba de hacer hace 2 segundos y no hay forma de que pueda explicar esto a los clientes, ya que aquellos que publican un artículo no están realmente relacionados con la actualización del contenido.

2) Crear un nuevo recurso también puede ser una opción, api / article- {action} / id, pero luego el recurso devuelto no sería article- {action} sino un artículo del que no estoy seguro si esto es correcto. También en el código del lado del servidor, la clase de artículo está manejando el trabajo real en ambos recursos y no estoy seguro de si esto va en contra del pensamiento RESTfull

Cualquier sugerencia es bienvenida.


Es perfectamente legal que las 'acciones' formen parte de un URI RESTful, si establecen una acción / algoritmo a realizar. ¿Qué tiene de malo api/article?action=publish? Los parámetros de consulta están destinados a casos en los que el estado del recurso depende del 'algoritmo' (o acción) que usted menciona. Por ejemplo, api/articles?sort=asces válido
PhD

1
Te sugiero que revises este artículo, que puede inspirarte con una solución aún más RESTANTE.
Benjol

Uno de los problemas que veo con api / article? Action = Publish es que en la aplicación RESTfull debería enviar TODOS los datos del artículo para su publicación, mientras que preferiría hacer esto: api / article / 4545 / Publish / sin nada adicional
Miro Svrtan

2
Excelente recurso sobre este tema y más: Diseño de API REST - Modelado de recursos
Izhaki

@PhD: si bien es perfectamente legal en el protocolo, los URI generalmente deberían ser sustantivos en lugar de verbos. Tener un verbo en el URI suele ser un signo de un mal diseño REST.
Mentira Ryan

Respuestas:


49

Encuentro útiles las prácticas descritas aquí :

¿Qué pasa con las acciones que no encajan en el mundo de las operaciones CRUD?

Aquí es donde las cosas pueden ponerse borrosas. Hay varios enfoques:

  1. Reestructurar la acción para que aparezca como un campo de un recurso. Esto funciona si la acción no toma parámetros. Por ejemplo, una acción de activación podría asignarse a un activatedcampo booleano y actualizarse mediante un PATCH al recurso.
  2. Trátelo como un recurso secundario con principios RESTful. Por ejemplo, la API de GitHub le permite protagonizas una esencia con PUT /gists/:id/stary no destacar con DELETE /gists/:id/star.
  3. A veces realmente no tienes forma de asignar la acción a una estructura RESTful sensible. Por ejemplo, una búsqueda de múltiples recursos realmente no tiene sentido que se aplique al punto final de un recurso específico. En este caso, /searchtendría más sentido aunque no sea un recurso. Esto está bien: solo haga lo correcto desde la perspectiva del consumidor de API y asegúrese de que esté documentado claramente para evitar confusiones.

Voto por el enfoque 2. Si bien pueden parecer recursos artificiales torpes para quienes llaman a la API, en realidad no tienen conocimiento de lo que está sucediendo en el servidor. Si llamo a POST /article/123/deactivationspara crear una nueva solicitud de desactivación para el artículo 123, es posible que el servidor no solo desactive el recurso solicitado, sino que en realidad almacene mi solicitud de desactivación para que pueda recuperar su estado más adelante.
JustAMartin

2
¿Por qué PUT /gists/:id/star no POST /gists/:id/star?
Filip Bartuzi

10
@FilipBartuzi Debido a que PUT es idempotente, es decir, no importa cuántas veces realice una acción con los mismos parámetros, el resultado es siempre el mismo (por ejemplo, si apaga y enciende una luz, cambia. Si intenta encenderla) nuevamente, nada cambia, ya está activado). POST no es idempotente, es decir, cada vez que realiza una acción, incluso con los mismos parámetros, la acción tiene un resultado diferente (por ejemplo, si envía una carta a alguien, esa persona recibe una carta. Si envía una carta idéntica a la misma persona, ahora tienen 2 letras).
Raphael

9

Las operaciones que resultan en cambios importantes de estado y comportamiento en el lado del servidor, como la acción "publicar" que describe, son difíciles de modelar explícitamente en REST. Una solución que a menudo veo es conducir ese comportamiento complejo implícitamente a través de los datos.

Considere ordenar productos a través de una API REST expuesta por un comerciante en línea. Ordenar es una operación compleja. Se empacarán y enviarán varios productos, se le cobrará a su cuenta y recibirá un recibo. Puede cancelar su pedido por un período de tiempo limitado y, por supuesto, existe una garantía completa de devolución de dinero que le permite devolver los productos para obtener un reembolso.

En lugar de una operación de compra compleja, dicha API podría permitirle crear un nuevo recurso, una orden de compra. Al principio, puede realizar las modificaciones que desee: agregar o eliminar productos, cambiar la dirección de envío, elegir otra opción de pago o cancelar su pedido por completo. Puede hacer todo esto porque aún no ha comprado nada, solo está manipulando algunos datos en el servidor.

Una vez que se completa su orden de compra y su período de gracia pasa, el servidor bloquea su orden para evitar más cambios. Solo en este momento comienza la compleja secuencia de operaciones, pero no puede controlarla directamente, solo indirectamente a través de los datos que colocó previamente en la orden de compra.

Según su descripción, "publicar" podría implementarse de esta manera. En lugar de exponer una operación, coloca una copia del borrador que ha revisado y desea publicar como un nuevo recurso en / publicar. Esto garantiza que las actualizaciones posteriores al borrador no se publicarán, incluso si la operación de publicación completa horas después.


La idea de cambiar todo el recurso de un artículo no publicado a un borrador encajaría exactamente en este caso, pero no encajaría en todas las demás acciones que existen en un recurso en general. ¿Se supone que REST lo manejará? Tal vez estoy abusando de él y solo debería usarlo como CRUD y nada más, como consultas SQL donde no espero que haya ninguna lógica dentro de la consulta misma.
Miro Svrtan

¿Por qué estoy preguntando todo esto? Bueno, desde hace algún tiempo, las aplicaciones web comienzan a ser multiplataforma y preferiría mantener una gran cantidad de lógica de negocios en el servidor, ya que la actualización de la lógica de negocios en iOS, Android, web, escritorio o cualquier otra plataforma que se me ocurra se hace imposible. rápidamente y me gustaría evitar todos los problemas de compatibilidad con versiones anteriores al cambiar una pequeña pieza de BL.
Miro Svrtan

2
Creo que REST puede manejar bien la lógica de negocios, pero no es tan adecuado para exponer la lógica de negocios existente escrita sin tener REST en mente. Es por eso que muchas compañías como Microsoft, SAP y otras a menudo exponen solo datos con operaciones CRUD, tal como usted dijo. Eche un vistazo al Protocolo de datos abiertos para ver cómo lo hacen.
Ferenc Mihaly

7

necesitamos enviar todos los datos del artículo a la API para su actualización y no se pudo implementar el trabajo multiusuario. Por ejemplo, el editor podría enviar datos 5 segundos más antiguos y sobrescribir la corrección que otro periodista acaba de hacer hace 2 segundos y no hay forma de que pueda explicar esto a los clientes, ya que aquellos que publican un artículo no están de ninguna manera conectados con la actualización del contenido.

Este tipo de cosas es un desafío, no importa lo que haga, es un problema muy similar al control de fuente distribuida (mercurial, git, etc.), y la solución, escrita en HTTP / ReST, se ve un poco similar.

Supongamos que tienes dos usuarios, Alice y Bob, ambos trabajando /articles/lunch. (para mayor claridad, la respuesta está en negrita)

Primero, Alice crea el artículo.

PUT /articles/lunch HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

301 Moved Permanently
Location: /articles/lunch/1 

El servidor no creó un recurso, porque no había una "versión" adjunta a la solicitud (suponiendo un identificador de /articles/{id}/{version}. Para realizar la creación, Alice fue redirigida a la url del artículo / versión que creará. Usuario de Alice El agente volverá a aplicar la solicitud en la nueva dirección.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?

201 Created

Y ahora el artículo ha sido creado. a continuación, Bob mira el artículo:

GET /articles/lunch HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

301 Moved Permanently
Location: /articles/lunch/1 

Bob mira allí:

GET /articles/lunch/1 HTTP/1.1
Host: example.com
Authorization: Basic Ym9iOnBhc3N3b3Jk

200 Ok
Content-Type: text/plain

Hey Bob, what do you want for lunch today?

Decide agregar su propio cambio.

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

301 Moved Permanently
Location: /articles/lunch/2

Al igual que con Alice, Bob es redirigido a donde creará una nueva versión.

PUT /articles/lunch/2 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic Ym9iOnBhc3N3b3Jk

Hey Bob, what do you want for lunch today?
Does pizza sound good to you, Alice?

201 Created

Finalmente, Alice decide que le gustaría agregar a su propio artículo:

PUT /articles/lunch/1 HTTP/1.1
Host: example.com
Content-Type: text/plain
Authorization: Basic YWxpY2U6c2VjcmV0

Hey Bob, what do you want for lunch today?
I was thinking about getting Sushi.

409 Conflict
Location: /articles/lunch/3
Content-Type: text/diff

---/articles/lunch/2
+++/articles/lunch/3
@@ 1,2 1,2 @@
 Hey Bob, what do you want for lunch today?
-Does pizza sound good to you, Alice?
+I was thinking about getting Sushi.

En lugar de ser redirigido de la forma normal, se devuelve un código de estado diferente al cliente 409, que le dice a Alice que la versión de la que estaba tratando de ramificarse ya se ha ramificado. Los nuevos recursos se crearon de todos modos (como se muestra en el Locationencabezado), y las diferencias entre los dos se incluyeron en el cuerpo de respuesta. Alice ahora sabe que la solicitud que acaba de hacer debe fusionarse de alguna manera.


Toda esta redirección está relacionada con la semántica de PUT, que requiere que se creen nuevos recursos exactamente donde lo solicita la línea de solicitud. esto también podría ahorrar un ciclo de solicitud utilizando POST, pero luego el número de versión tendría que estar codificado en la solicitud por alguna otra magia, lo que me pareció menos obvio para fines ilustrativos, pero probablemente aún sería preferido en una API real para minimizar los ciclos de solicitud / respuesta.


1
Versar no fue un problema aquí, lo acabo de decir como ejemplo de posibles problemas si se usa el artículo como un recurso para publicar acciones
Miro Svrtan

3

Aquí hay otro ejemplo que no trata el contenido de los documentos, sino más bien el estado transitorio. (Encuentro el control de versiones, dado que, en general, cada versión puede ser un nuevo recurso, un tipo de problema fácil).

Digamos que quiero exponer un servicio que se ejecuta en una máquina a través de un REST para que pueda detenerse, iniciarse, reiniciarse, etc.

¿Cuál es el enfoque más RESTANTE aquí? POST / service? Command = restart, por ejemplo? ¿O POST / service / state con un cuerpo de, por ejemplo, 'corriendo'?

Sería bueno codificar las mejores prácticas aquí y si REST es el enfoque correcto para este tipo de situación.

En segundo lugar, supongamos que quiero impulsar alguna acción desde un servicio que no afecta a su propio estado, sino que desencadena un efecto secundario. Por ejemplo, un servicio de correo que envía un informe, creado en el momento de la llamada, a un montón de direcciones de correo electrónico.

GET / report podría ser una forma de obtener una copia del informe yo mismo; pero ¿qué pasa si queremos impulsar al lado del servidor otras acciones como enviar correos electrónicos como digo anteriormente? O escribiendo en una base de datos.

Estos casos bailan en torno a la división de recursos y acciones, y veo formas de manejarlos de una manera orientada a REST, pero, francamente, se siente como un truco para hacerlo. Quizás la pregunta clave es si una API REST debería admitir efectos secundarios en general.


2

REST está orientado a datos y, como tal, los recursos funcionan mejor como "cosas" que como acciones. La semántica implícita de los métodos http; OBTENER, PONER, BORRAR, etc. sirven para reforzar la orientación. POST, por supuesto, es la excepción.

Un recurso puede ser una mezcla de datos, es decir. contenido del artículo; y metadatos, es decir. publicado, bloqueado, revisión. Hay muchas otras formas posibles de dividir los datos, pero primero debe analizar cómo se verá el flujo de datos para determinar cuál es el más óptimo (si lo hay). Por ejemplo, puede ser que las revisiones sean su propio recurso según el artículo, como sugiere TokenMacGuy.

Con respecto a la implementación, probablemente haría algo como lo que sugiere TockenMacGuy. También agregaría campos de metadatos en el artículo, no revisión, como 'bloqueado' y 'publicado'.


1

No pienses que está manipulando directamente el estado del artículo. En cambio, está poniendo una orden de cambio solicitando que se cree el artículo.

Puede modelar la colocación de una orden de cambio como la creación de un nuevo recurso de orden de cambio (POST). Hay muchas ventajas. Por ejemplo, puede especificar una fecha y hora futuras en que el artículo debe publicarse como parte de la orden de cambio, y dejar que el servidor se preocupe por cómo se implementa.

Si la publicación no es un proceso instantáneo, no tiene que esperar a que termine antes de volver al cliente. Simplemente reconoce que se creó la orden de cambio y devuelve el ID de la orden de cambio. Luego puede usar la URL correspondiente a esa orden de cambio para compartir el estado de la orden de cambio.

Una idea clave para mí fue reconocer que esta metáfora del orden de cambio es solo otra forma de describir la programación orientada a objetos. En lugar de recursos, llamamos a los objetos. En lugar de órdenes de cambio, los llamamos mensajes. Una forma de enviar un mensaje de A a B en OO es hacer que A llame a un método en B. Otra forma de hacerlo, particularmente cuando A y B están en computadoras diferentes, es hacer que A cree un nuevo objeto, M y envíelo a B. REST simplemente formaliza ese proceso.


De hecho, consideraría esto más cerca del modelo de actor que OO. Al considerar las solicitudes REST como mensajes (que son), las alinea muy bien con el aprovisionamiento de eventos para administrar el estado. REST divide cuidadosamente sus interacciones a lo largo de las líneas CQRS. Los mensajes GET son la consulta, POST, PUT, PATCH, DELETE en el área de comando.
WillD

0

Si te entiendo correctamente, creo que lo que tienes es más una cuestión de determinación de "regla de negocios" que una cuestión técnica.

El hecho de que un artículo se puede sobrescribir podría resolverse mediante la introducción de niveles de autorización en los que los usuarios mayores pueden anular las versiones de los usuarios menores. También mediante la introducción de versiones y una columna para capturar el estado del artículo (por ejemplo, "en desarrollo", "final" , etc.), podrías superar esto. También puede darle al usuario la posibilidad de seleccionar una versión dada, ya sea por una combinación de tiempo de envío y por el número de versión.

En todos los casos anteriores, su servicio debe implementar las reglas comerciales que establezca. Por lo tanto, puede llamar al servicio con los parámetros: ID de usuario, artículo, versión, acción (donde la versión es opcional, nuevamente esto depende de las reglas de su negocio).


No creo que esta sea una regla de negocios, sino estrictamente técnica. La idea de agregar una versión es buena para ayudar a anular las reglas, pero aún no resuelve la situación de que actualizar contenido y publicar contenido no son acciones relacionadas.
Miro Svrtan
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.