¿Cómo se ajusta una API REST para un dominio basado en comandos / acciones?

24

En este artículo, el autor afirma que

A veces, se requiere exponer una operación en la API que inherentemente no es RESTful.

y eso

Si una API tiene demasiadas acciones, es una indicación de que fue diseñada con un punto de vista RPC en lugar de usar principios RESTful, o que la API en cuestión es, naturalmente, una mejor opción para un modelo de tipo RPC.

Esto refleja lo que he leído y escuchado en otros lugares también.

Sin embargo, esto me parece bastante confuso y me gustaría obtener una mejor comprensión del asunto.

Ejemplo I: cerrar una máquina virtual a través de una interfaz REST

Creo que hay dos formas fundamentalmente diferentes de modelar el apagado de una VM. Cada camino puede tener algunas variaciones, pero concentrémonos en las diferencias más fundamentales por ahora.

1. Parchear la propiedad del estado del recurso

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

(Alternativamente, PUTen el subrecurso /api/virtualmachines/42/state).

La VM se apagará en segundo plano y en algún momento posterior, dependiendo de si el apagado se realizará correctamente o no, el estado podría actualizarse internamente con "apagado".

2. PUT o POST en la propiedad de acciones del recurso

PUT /api/virtualmachines/42/actions
Content-Type:application/json  

{ "type": "shutdown" }

El resultado es exactamente el mismo que en el primer ejemplo. El estado se actualizará a "apagado" de inmediato y tal vez eventualmente a "apagado".

¿Ambos diseños son RESTful?

¿Qué diseño es mejor?

Ejemplo II: CQRS

¿Qué sucede si tenemos un dominio CQRS con muchas de esas "acciones" (también conocidas como comandos) que podrían conducir a actualizaciones de múltiples agregados o no pueden asignarse a operaciones CRUD en recursos y subrecursos concretos?

¿Deberíamos tratar de modelar tantos comandos como concreto crea o actualiza en recursos concretos, siempre que sea posible (siguiendo el primer enfoque del ejemplo I) y usar "puntos finales de acción" para el resto?

¿O deberíamos asignar todos los comandos a puntos finales de acción (como en el segundo enfoque del ejemplo I)?

¿Dónde deberíamos dibujar la línea? ¿Cuándo el diseño se vuelve menos RESTANTE?

¿Es un modelo CQRS más adecuado para un RPC como API?

De acuerdo con el texto citado arriba, según tengo entendido.

Como puede ver en mis muchas preguntas, estoy un poco confundido sobre este tema. ¿Me pueden ayudar a comprenderlo mejor?

Leifbattermann
fuente
"crear una acción" no parece RESTful, excepto si la acción ejecutada tiene su propio identificador de recursos posteriormente. De lo contrario, cambiar la propiedad "estado" a través de PATCH o PUT tiene más sentido. Para la parte de CQRS, todavía no tengo una buena respuesta.
Fabian Schmengler
3
@Laiv No hay nada malo en eso. Es una pregunta académica, me gustaría obtener una comprensión más profunda del asunto.
leifbattermann

Respuestas:

19

En el primer caso (apagado de máquinas virtuales), no consideraría ninguna de las alternativas OP RESTful. Por supuesto, si usa el modelo de madurez de Richardson como criterio, ambos son API de nivel 2 porque usan recursos y verbos.

Sin embargo, ninguno de ellos usa controles hipermedia, y en mi opinión, ese es el único tipo de REST que diferencia el diseño de API RESTful de RPC. En otras palabras, quédese con el nivel 1 y 2, y en la mayoría de los casos tendrá una API de estilo RPC.

Para modelar dos formas diferentes de apagar una VM, expondría la VM como un recurso que (entre otras cosas) contiene enlaces:

{
    "links": [{
        "rel": "shut-down",
        "href": "/vms/1234/fdaIX"
    }, {
        "rel": "power-off",
        "href": "/vms/1234/CHTY91"
    }],
    "name": "Ploeh",
    "started": "2016-08-21T12:34:23Z"
}

Si un cliente desea cerrar la PloehVM, debe seguir el enlace con el shut-downtipo de relación. (Normalmente, como se describe en el RESTful Web Services Cookbook , usaría un IRI o un esquema de identificación más elaborado para los tipos de relación, pero elegí mantener el ejemplo lo más simple posible).

En este caso, hay poca otra información para proporcionar con la acción, por lo que el cliente simplemente debe hacer una POST vacía contra la URL en href:

POST /vms/1234/fdaIX HTTP/1.1

(Dado que esta solicitud no tiene cuerpo, sería tentador modelar esto como una solicitud GET, pero las solicitudes GET no deberían tener efectos secundarios observables, por lo que POST es más correcto).

Del mismo modo, si un cliente quiere apagar la VM, seguirá el power-offenlace en su lugar.

En otras palabras, los tipos de relación de los enlaces proporcionan posibilidades que indican la intención. Cada tipo de relación tiene un significado semántico específico. Esta es la razón por la que a veces hablamos de la web semántica .

Para mantener el ejemplo lo más claro posible, oscurecí intencionalmente las URL en cada enlace. Cuando el servidor de alojamiento recibe la solicitud entrante, se sabría que fdaIXlos medios cerrados , y CHTY91los medios de apagado .

Normalmente, solo codificaría la acción en la propia URL, de modo que las URL serían /vms/1234/shut-downy /vms/1234/power-off, pero al enseñar, eso borra la distinción entre los tipos de relación (semántica) y las URL (detalles de implementación).

Dependiendo de los clientes que tenga, puede considerar hacer URL RESTful no pirateables .

CQRS

Cuando se trata de CQRS, una de las pocas cosas en las que Greg Young y Udi Dahan están de acuerdo es que CQRS no es una arquitectura de alto nivel . Por lo tanto, sería cauteloso al hacer una API RESTful demasiado similar a CQRS, porque eso significaría que los clientes se convierten en parte de su arquitectura.

A menudo, la fuerza impulsora detrás de una API RESTful real (nivel 3) es que desea poder evolucionar su API sin romper clientes y sin tener el control de los clientes. Si esa es su motivación, entonces CQRS no sería mi primera opción.

Mark Seemann
fuente
¿Quiere decir que los primeros ejemplos no son RESTful porque no usan controles hipermedia? Pero ni siquiera publiqué ninguna respuesta, solo las URL de solicitud y los cuerpos.
leifbattermann
44
@leifbattermann No son RESTful porque usan el cuerpo del mensaje para comunicar la intención; eso es claramente RPC. Si utilizó enlaces para llegar a esos recursos, ¿por qué necesitaría comunicar la intención a través del cuerpo?
Mark Seemann
Eso tiene sentido. ¿Por qué sugieres una POST? ¿No es la acción idempotente? En cualquier caso, ¿cómo le dice a su cliente qué método HTTP debe usar?
leifbattermann
2
@ guillaume31 me DELETEparece extraño porque después de apagar el vm seguirá existiendo, solo en estado "apagado" (o algo así).
leifbattermann
1
El recurso no tiene que reflejar una VM atemporalmente, puede representar una instancia de ejecución del mismo.
guillaume31
6

Apagar una VM a través de una interfaz REST

Este es en realidad un ejemplo algo famoso, presentado por Tim Bray en 2009 .

Roy Fielding, discutiendo el problema, compartió esta observación :

Personalmente, prefiero los sistemas que tratan el estado monitoreado (como el estado de energía) como no editable.

En resumen, tiene un recurso de información que devuelve una representación actual del estado supervisado; esa representación puede incluir un enlace hipermedia a un formulario para solicitar un cambio a ese estado, y el formulario tiene otro enlace a un recurso para manejar (cada) solicitud de cambio.

Seth Ladd tenía las ideas clave sobre el problema.

Hemos convertido la ejecución de un estado simple de una persona en un nombre verdadero que se puede crear, actualizar y comentar.

Llevando esto de vuelta a las máquinas de reinicio. Yo diría que POSTARÍAS en / vdc / 434 / cluster / 4894 / server / 4343 / reboots Una vez que hayas publicado, tienes un URI que representa este reinicio, y puedes OBTENERLO para actualizaciones de estado. A través de la magia del hipervínculo, la representación del reinicio se vincula al servidor que se reinicia.

Creo que acuñar espacio URI es barato, y los URI son aún más baratos. ¡Crea una colección de actividades, modeladas como sustantivos, y POST, PUT y DELETE!

La programación RESTful es la burocracia de Vogon a escala web. ¿Cómo haces algo RESTful? Inventar nuevos documentos para ello y digitalizar el papeleo.

En un lenguaje algo más sofisticado, lo que está haciendo es definir el protocolo de aplicación de dominio para "apagar una VM" e identificar los recursos que necesita para exponer / implementar ese protocolo

Mirando tus propios ejemplos

PATCH /api/virtualmachines/42
Content-Type:application/json  

{ "state": "shutting down" }

Está bien; en realidad no está tratando la solicitud en sí como su propio recurso de información por separado, pero aún podría administrarla.

Te has perdido un poco en tu representación del cambio.

Sin embargo, con PATCH, la entidad adjunta contiene un conjunto de instrucciones que describen cómo se debe modificar un recurso que actualmente reside en el servidor de origen para producir una nueva versión.

Por ejemplo, el tipo de medio JSON Patch formatea las instrucciones como si estuviera modificando directamente un documento JSON

[
    { "op": "replace", "path": "state", "value": "shutting down" }
]

En su alternativa, la idea es cercana, pero obviamente no es correcta. PUTes un reemplazo completo del estado del recurso en la URL de destino , por lo que probablemente no elegiría una ortografía que parezca una colección como el objetivo de una representación de una sola entidad.

POST /api/virtualmachines/42/actions

Es coherente con la ficción de que estamos agregando una acción a una cola

PUT /api/virtualmachines/42/latestAction

Es coherente con la ficción de que estamos haciendo una actualización del elemento de cola en la cola; Es un poco extraño hacerlo de esta manera. El principio de menor sorpresa recomienda dar a cada PUT su propio identificador único, en lugar de ponerlos a todos en un solo lugar y modificar múltiples recursos al mismo tiempo.

Tenga en cuenta que, en la medida en que estamos discutiendo la ortografía de URI, a REST no le importa; /cc719e3a-c772-48ee-b0e6-09b4e7abbf8bes un URI perfectamente cromulento en lo que respecta a REST. La legibilidad, como con los nombres de variables, es una preocupación separada. El uso de ortografías que sean consistentes con RFC 3986 hará que las personas sean mucho más felices.

CQRS

¿Qué sucede si tenemos un dominio CQRS con muchas de esas "acciones" (también conocidas como comandos) que podrían conducir a actualizaciones de múltiples agregados o no pueden asignarse a operaciones CRUD en recursos y subrecursos concretos?

Greg Young en CQRS

CQRS es un patrón muy simple que permite muchas oportunidades para la arquitectura que de otro modo no existirían. CQRS no es una coherencia eventual, no está produciendo eventos, no está enviando mensajes, no tiene modelos separados para leer y escribir, ni está utilizando el abastecimiento de eventos.

Cuando la mayoría de las personas habla de CQRS, en realidad están hablando de aplicar el patrón CQRS al objeto que representa el límite de servicio de la aplicación.

Dado que está hablando de CQRS en el contexto de HTTP / REST, parece razonable suponer que está trabajando en este último contexto, así que vamos con eso.

Este, sorprendentemente, es incluso más fácil que su ejemplo anterior. La razón de esto es simple: los comandos son mensajes .

Jim Webber describe HTTP como el protocolo de aplicación de una oficina de la década de 1950; el trabajo se realiza tomando mensajes y colocándolos en bandejas de entrada. La misma idea es válida: obtenemos una copia en blanco de un formulario, lo completamos con los detalles que conocemos y lo entregamos. Ta da

¿Deberíamos tratar de modelar tantos comandos como concreto crea o actualiza en recursos concretos, siempre que sea posible (siguiendo el primer enfoque del ejemplo I) y usar "puntos finales de acción" para el resto?

Sí, en la medida en que los "recursos concretos" son mensajes, en lugar de entidades en el modelo de dominio.

Idea clave: su API REST sigue siendo una interfaz ; debería poder cambiar el modelo subyacente sin que los clientes necesiten cambiar los mensajes. Cuando lanza un nuevo modelo, lanza una nueva versión de sus puntos finales web que saben cómo tomar su protocolo de dominio y aplicarlo al nuevo modelo.

¿Es un modelo CQRS más adecuado para un RPC como API?

En realidad no, en particular, los cachés web son un gran ejemplo de un "modelo de lectura eventualmente consistente". Hacer que cada una de sus vistas sea direccionable de forma independiente, cada una con sus propias reglas de almacenamiento en caché, le brinda un montón de escalado de forma gratuita. Hay relativamente poco atractivo para un enfoque exclusivamente RPC para las lecturas.

Para las escrituras, es una pregunta clave: enviar todos los comandos a un único controlador en un único punto final, o una sola familia de puntos finales, es ciertamente más fácil . REST es realmente más sobre cómo encontrar la comunicación del punto final al cliente.

Tratar un mensaje como su propio recurso único tiene la ventaja de que puede usar PUT, alertando a los componentes intermediarios sobre el hecho de que el manejo del mensaje es idempotente, para que puedan participar en ciertos casos de manejo de errores. . (Nota: desde el punto de vista de los clientes, si los recursos tienen un URI diferente, entonces son recursos diferentes; el hecho de que todos puedan tener el mismo código de controlador de solicitud en el servidor de origen es un detalle de implementación oculto por el uniforme interfaz).

Fielding (2008)

También debo tener en cuenta que lo anterior aún no es completamente RESTful, al menos cómo uso el término. Todo lo que he hecho es describir las interfaces de servicio, que no es más que cualquier RPC. Para que sea RESTful, necesitaría agregar hipertexto para presentar y definir el servicio, describir cómo realizar el mapeo usando formularios y / o plantillas de enlaces, y proporcionar código para combinar las visualizaciones de maneras útiles.

VoiceOfUnreason
fuente
2

Puede aprovechar 5 niveles de tipo de medio para especificar el comando en el campo de encabezado de tipo de contenido de la solicitud.

En el ejemplo de VM, sería algo así.

PUT /api/virtualmachines/42
Content-Type:application/json;domain-model=PowerOnVm

> HTTP/1.1 201 Created
Location: /api/virtualmachines/42/instance

Luego

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=ShutDownVm

O

DELETE /api/virtualmachines/42/instance
Content-Type:application/json;domain-model=PowerOffVm

Ver https://www.infoq.com/articles/rest-api-on-cqrs

guillaume31
fuente
Tenga en cuenta que 5LMT fue una solución propuesta y no es compatible con los estándares . Me encontré con el artículo de CQRS antes y aprendí mucho de él.
Peter L
1

El ejemplo en el artículo vinculado se basa en la idea de que iniciar la máquina y apagarla debe estar dirigida por comandos en lugar de por cambios en el estado de los recursos modelados. Esto último es más o menos lo que REST vive y respira. Un mejor modelado de la VM requiere una mirada a cómo funciona su contraparte en el mundo real y cómo usted, como humano, interactuaría con ella. Esto es de largo aliento, pero creo que da una buena idea del tipo de pensamiento requerido para hacer un buen modelado.

Hay dos formas de controlar el estado de alimentación de la computadora en mi escritorio:

  • Interruptor de encendido: corta de inmediato el flujo de electricidad a la fuente de alimentación, lo que hace que toda la computadora se detenga de forma repentina y desordenada.
  • Boton de encendido / apagado: Le dice al hardware que notifique al software que algo en el exterior quiere que todo se cierre. El software realiza un apagado ordenado, notifica al hardware que está hecho y el hardware le indica a la fuente de alimentación que puede pasar a su estado de espera. Si el interruptor de encendido está encendido, la máquina está funcionando y el software está en un estado en el que no puede responder a la señal de apagado, el sistema no se apagará a menos que apague el interruptor de encendido. (Una VM se comportará exactamente de la misma manera; si el software ignora la señal de apagado, la máquina continuará funcionando, tengo que forzarla a apagarse). Si quiero poder reiniciar la máquina, tengo que Vuelva a encender el interruptor de encendido y luego presione el botón de encendido / apagado. (Muchas computadoras tienen la opción de presionar prolongadamente el botón de encendido para ir directamente al estado de espera, pero este modelo realmente no necesita eso). Este botón se puede tratar como un interruptor de palanca porque presionarlo produce un comportamiento diferente según el estado cuando se presiona. Si el interruptor de encendido está apagado, presionar este botón no hace absolutamente nada.

Para una VM, ambos pueden modelarse como valores booleanos de lectura / escritura:

  • power- Cuando se cambia a true, no sucede nada más que una nota de que el interruptor se ha colocado en este estado. Cuando se cambia a false, la VM se ordena en un estado de apagado inmediato. En aras de la integridad, si el valor no cambia después de una escritura, no sucede nada.

  • onoff- Cuando se cambia a true, no sucede nada si poweres así false; de lo contrario, se ordena al VM que se inicie. Cuando se cambia a false, no sucede nada si poweres así false; de lo contrario, se le ordena a la VM que notifique al software que realice un apagado ordenado, lo que hará y luego notifique a la VM que puede pasar al estado de apagado. Nuevamente, para completar, una escritura sin cambios no hace nada.

Con todo esto, nos damos cuenta de que hay una situación en la que el estado de la máquina no refleja el estado de los interruptores, y eso es durante el apagado. powerseguirá siendo truey onoffseguirá siendo false, pero el procesador todavía está ejecutando su apagado, y para eso necesitamos agregar otro recurso para que podamos saber qué está haciendo realmente la máquina:

  • running- Un valor de solo lectura que es truecuando la VM se está ejecutando y falsecuando no, se determina preguntando al hipervisor su estado.

El resultado de esto es que si quieres una máquina virtual para poner en marcha, hay que asegurarse de que el powery onofflos recursos se han establecido true. (Puede permitir powerque se salte el paso haciendo que se reinicie automáticamente, de modo que si se establece en false, se convierte truedespués de que la VM se haya detenido de manera verificable. Si eso es algo RESTfully-puro que hacer es forraje para otra discusión). Si usted quiere que haga un cierre ordenado, se establece onoffa falsey volver más tarde para ver si la máquina se detuvo en realidad, el establecimiento powerde falsesi no lo hizo.

Al igual que en el mundo real, todavía tiene el problema de que se le indique que inicie la VM después de que se haya onoffcambiado, falsepero todavía se runningdebe a que está en el medio del apagado. Cómo lidiar con eso es una decisión política.

Blrfl
fuente
0

¿Ambos diseños son RESTful?

Entonces, si quieres pensar tranquilamente, entonces olvídate de los comandos. El cliente no le dice al servidor que apague la VM. El cliente "cierra" (metafóricamente) su copia de la representación de recursos actualizando su estado y luego PONE esa representación nuevamente en el servidor. El servidor acepta esa nueva representación de estado y, como efecto secundario de esto, en realidad apaga la VM. El aspecto del efecto secundario se deja en manos del servidor.

Es menos de

Hola servidor, cliente aquí, ¿te importaría apagar la VM?

y más de

Hola servidor, cliente, actualicé el estado del recurso VM 42 en el estado de apagado, actualicé tu copia de este recurso y luego hice lo que creas apropiado

Como efecto secundario de que el servidor acepta este nuevo estado, puede verificar qué acciones tiene que realizar realmente (como apagar físicamente VM 42), pero esto es transparente para el cliente. El cliente no se preocupa por las acciones que el servidor debe tomar para ser coherente con este nuevo estado

Así que olvídate de los comandos. Los únicos comandos son los verbos en HTTP para la transferencia de estado. El cliente no sabe, ni le importa, cómo el servidor llevará la VM física al estado de apagado. El cliente no está emitiendo comandos al servidor para lograr esto, solo está diciendo que este es el nuevo estado, descúbrelo .

El poder de esto es que desacopla al cliente del servidor en términos de control de flujo. Si más tarde el servidor cambia la forma en que funciona con las máquinas virtuales, al cliente no le importa. No hay puntos finales de comando para actualizar. En RPC, si cambia el punto final de API de shutdowna shut-down, ha roto todos sus clientes, ya que ahora no conocen el comando para llamar al servidor.

REST es similar a la programación declarativa, en la que en lugar de enumerar un conjunto de instrucciones para cambiar algo, simplemente establece cómo desea que sea y deja que el entorno de programación lo descubra.

Cormac Mulhall
fuente
Gracias por tu respuesta. La segunda parte sobre el desacoplamiento del cliente y el servidor se alinea mucho con mi propio entendimiento. ¿Tiene un recurso / enlace que respalde la primera parte de su respuesta? ¿Qué restricción REST se rompe exactamente si uso recursos, métodos HTTP, hipermedia, mensajes autodescriptivos, etc.?
leifbattermann
No hay ningún problema con el uso de recursos, métodos HTTP, etc. Después de todo, HTTP es un protocolo RESTful. Donde surge un problema es usar lo que usted llama "puntos finales de acción". En REST hay recursos que representan conceptos o cosas (como la máquina virtual 42 o mi cuenta bancaria) y los verbos HTTP se utilizan para transferir el estado de estos recursos entre clientes y servidores. Eso es. Lo que no debe hacer es intentar crear nuevos comandos combinando puntos finales que no sean de recursos con verbos HTTP. Entonces 'virtualmachines / 42 / actions' no es un recurso y no debería existir en un sistema RESTful.
Cormac Mulhall
O para decirlo de otra manera, el cliente no debería intentar ejecutar comandos en el servidor (más allá de los verbos HTTP limitados relacionados únicamente con la transferencia de recursos del estado). El cliente debe actualizar su copia del recurso y luego simplemente pedirle al servidor que acepte este nuevo estado. La aceptación de este nuevo estado puede tener efectos secundarios (la VM 42 está físicamente apagada) pero eso está más allá de la preocupación del cliente. Si el cliente no está tratando de ejecutar comandos en el servidor, entonces no hay acoplamiento a través de esos comandos entre el cliente y el servidor.
Cormac Mulhall
Puede ejecutar el comando en el recurso ... ¿Cómo haría, digamos "depositar" y "retirar" en una cuenta bancaria? Usaría CRUD para algo que no es CRUD.
Konrad
Es mejor usar en POST /api/virtualmachines/42/shutdownlugar de tener cualquier "efecto secundario". La API debe ser comprensible para el usuario, ¿cómo sabré que, por ejemplo DELETE /api/virtualmachines/42, apagará la VM? Un efecto secundario para mí es un error, debemos diseñar nuestras API para que sean comprensibles y autodescriptivas
Konrad