RESTFul: estado cambiando acciones

52

Estoy planeando construir una API RESTfull pero hay algunas preguntas de arquitectura que están creando algunos problemas en mi cabeza. Agregar una lógica de negocio de back-end a los clientes es una opción que me gustaría evitar ya que la actualización de múltiples plataformas de clientes es difícil de mantener 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 deberíamos implementar acciones como publicar, anular la publicación, activar o desactivar, etc., pero intentar mantenerlo lo más simple posible?

1) Deberíamos usar api / article / {id} / {action}, ya que allí puede suceder mucha lógica de backend, como empujar a ubicaciones remotas o cambiar de propiedades múltiples. Probablemente, lo más difícil aquí es que debemos enviar todos los datos del artículo a la API para la actualización y no se pudo implementar el trabajo multiusuario. Por ejemplo, el editor podría enviar datos anteriores de 5 segundos y sobrescribir la corrección que algún otro periodista acaba de hacer hace 2 segundos y no hay forma de que pueda explicárselo a los clientes, ya que aquellos que publican un artículo no están relacionados de ninguna manera con la actualización del contenido.

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

Cualquier sugerencia es bienvenida ..

    
pregunta Miro Svrtan 25.03.2012 - 12:12

7 respuestas

38

Encuentro que las prácticas descritas aquí son útiles:

  

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

     

Aquí es donde las cosas pueden ponerse borrosas. Hay una serie de enfoques:

     
  1. Reestructure la acción para que aparezca como un campo de un recurso. Esta   Funciona si la acción no toma parámetros. Por ejemplo, un activar   la acción podría asignarse a un campo booleano activated y actualizarse a través de   Un PARCHE al recurso.
  2.   
  3. Trátelo como un sub-recurso con RESTful   principios Por ejemplo, la API de GitHub te permite iniciar una actividad con    PUT /gists/:id/star y unstar con DELETE /gists/:id/star .
  4.   
  5. A veces, realmente no tiene forma de asignar la acción a un RESTO completo.   estructura. Por ejemplo, una búsqueda de múltiples recursos realmente no hace   sentido para ser aplicado a un punto final del recurso específico. En este caso,    /search tendría más sentido aunque no sea un recurso.   Esto está bien, solo haga lo correcto desde la perspectiva de la API   consumidor y asegúrese de que esté documentado claramente para evitar confusiones.
  6.   
    
respondido por el Tim 17.01.2015 - 20:24
9

Las operaciones que resultan en cambios importantes de estado y comportamiento en el lado del servidor como la acción de "publicación" 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 empaquetará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 de devolución de dinero completa que le permite devolver productos para un reembolso.

En lugar de una operación de compra compleja, tal 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. Puedes hacer todo esto porque todavía no has comprado nada, solo estás manipulando algunos datos en el servidor.

Una vez que se completa su orden de compra y transcurre su período de gracia, el servidor bloquea su orden para evitar nuevos cambios. Solo en este momento se inicia 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 has revisado y quieres publicar como nuevo recurso en / publish. Esto garantiza que las actualizaciones posteriores al borrador no se publicarán, incluso si la operación de publicación se completa horas después.

    
respondido por el Ferenc Mihaly 25.03.2012 - 16:20
6
  

debemos enviar todos los datos del artículo a la API para actualizarlos y no se pudo implementar el trabajo multiusuario. Por ejemplo, el editor podría enviar datos anteriores de 5 segundos y sobrescribir la corrección que algún otro periodista acaba de hacer hace 2 segundos y no hay forma de que pueda explicárselo a los clientes, ya que aquellos que publican un artículo no están conectados de ninguna manera para actualizar el contenido.

Este tipo de cosas es un desafío sin importar lo que hagas, es un problema muy similar al control de fuente distribuido (mercurial, git, etc.), y la solución, escrita en HTTP / ReST, parece un poco similar.

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

Primero, Alicia 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, (asumiendo un identificador de /articles/{id}/{version} . Para realizar la creación, Alice fue redirigida a la url del artículo / versión que ' El agente de usuario de Alice 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?

Él 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 se redirige 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 como de costumbre, se devuelve un código de estado diferente al cliente, 409 , que le dice a Alice que la versión de la que estaba intentando derivar ya se ha ramificado. Los nuevos recursos se crearon de todos modos (como se muestra en el encabezado Location ), y las diferencias entre los dos se incluyeron en el cuerpo de la respuesta. Alice ahora sabe que la solicitud que acaba de hacer debe fusionarse de alguna manera.

Todo este redireccionamiento está relacionado con la semántica de PUT , que requiere que se creen nuevos recursos exactamente donde la línea de solicitud solicita. esto también podría guardar un ciclo de solicitud usando POST en su lugar, pero entonces 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 los propósitos de ilustración, pero probablemente todavía se prefiera en una API real para minimizar los ciclos de solicitud / respuesta.

    
respondido por el SingleNegationElimination 25.03.2012 - 17:32
2

REST está orientado a los datos y, como tales, los recursos funcionan mejor como "cosas" y no como acciones. La semántica implícita de los métodos http; GET, PUT, DELETE, 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 articulo; y metadatos es decir. publicado, bloqueado, revisión. Hay muchas otras formas posibles de dividir los datos, pero tiene que analizar cómo se verá el flujo de datos primero para determinar cuál es el más óptimo (si existe). Por ejemplo, puede ser que las revisiones deban ser su propio recurso en virtud del 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 en la revisión, como "bloqueado" y "publicado".

    
respondido por el dietbuddha 26.03.2012 - 06:30
2

Aquí hay otro ejemplo que trata no con el contenido de los documentos, sino más bien con el estado transitorio. (Considero que el control de versiones, dado que, en general, cada versión puede ser un nuevo recurso, una especie 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 completo aquí? POST / service? Command = reiniciar, por ejemplo? ¿O POST / service / state con un cuerpo de, digamos, '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 de un servicio que no afecta 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 un correo electrónico como dije anteriormente? O escribiendo a una base de datos.

Estos casos bailan alrededor de la brecha recurso-acción, y veo formas de manejarlos de una manera orientada a REST, pero francamente parece un poco como un truco al hacerlo. Quizás la pregunta clave sea si una API REST debería admitir efectos secundarios en general.

    
respondido por el AMD 22.04.2013 - 07:33
1

No pienses en ello como una manipulación directa del estado del artículo. En su lugar, está colocando una orden de cambio solicitando que se cree el artículo.

Puede modelar la colocación en una orden de cambio como creando un nuevo recurso de orden de cambio (POST). Hay muchas ventajas. Por ejemplo, puede especificar una fecha y hora futuras en las que el artículo debería 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 la orden de cambio se creó 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 que reconocer esta metáfora de orden de cambio es solo otra forma de describir la programación orientada a objetos. En lugar de recursos, llamamos entonces 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.

    
respondido por el Patrick McElhaney 29.06.2017 - 02:51
0

Si lo entiendo correctamente, creo que lo que tiene es más un problema de determinación de "regla de negocios" que un problema técnico.

El hecho de que se pueda sobrescribir un artículo podría resolverse mediante la introducción de niveles de autorización en los que los usuarios mayores puedan anular las versiones de los usuarios junior. 'final', etc.), podrías superar esto. También puede dar 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 de negocio 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, de nuevo, esto depende de sus reglas comerciales).

    
respondido por el NoChance 25.03.2012 - 13:47

Lea otras preguntas en las etiquetas