Abastecimiento de eventos y REST

17

Me encontré con el diseño de Event Sourcing y me gustaría usarlo en una aplicación donde se necesita un cliente REST (RESTful para ser precisos). Sin embargo, no puedo conectarlos, ya que REST es bastante CRUDO y el abastecimiento de eventos se basa en tareas. Me preguntaba cómo puede diseñar la creación de comandos basados ​​en solicitudes al servidor REST. Considere este ejemplo:

Con REST puede poner un nuevo estado al recurso llamado Archivo. En una solicitud, puede enviar un nuevo nombre de archivo, puede cambiar la carpeta principal y / o cambiar el propietario del archivo, etc.

Cómo construir el servidor para que pueda usar el abastecimiento de eventos. Estaba pensando en estas posibilidades:

  1. Determinar el servidor de los campos que se han cambiado y crear comandos apropiados ( RenameFileCommand, MoveFileCommand, ChangeOwnerCommand, ...) y envían estos de forma individual. Sin embargo, en esta configuración, cada uno de los comandos puede fallar dejando a otros fuera de la transacción y, por lo tanto, fuera del cambio "atómico" del recurso.

  2. Despacho sólo un comando ( UpdateFileCommand) y en el controlador de comandos, más precisamente en el agregado, determinar qué campos se han cambiado y enviar eventos individuales en lugar ( FileRenamedEvent, FileMovedEvent, OwnerChangedEvent, ...)

  3. Este no me gusta en absoluto: en la solicitud al servidor, especificaría en los encabezados qué comando usar, porque la interfaz de usuario todavía está basada en tareas (pero la comunicación se realiza a través de REST). Sin embargo, fallará en cualquier otro uso de la comunicación REST (por ejemplo, en aplicaciones externas) ya que no están obligados a cambiar solo un campo en una solicitud. También traigo un acoplamiento bastante grande en la interfaz de usuario, REST y back-end basado en ES.

¿Cuál preferirías o hay alguna forma mejor de manejar esto?

Nota al margen: aplicación escrita en Java y Axon Framework para el abastecimiento de eventos.

pelirrojo
fuente
Ciertamente no el 3er. Haría el primero, pero tengo que pensarlo.
inf3rno
¿Tiene dudas sobre si una única solicitud HTTP puede generar múltiples Comandos? ¿Entiendo bien? En mi humilde opinión. debería poder hacerlo, pero no he leído sobre DDD por un tiempo, así que tengo que verificar un código de muestra sobre cómo implementar esto.
inf3rno
Encontré un ejemplo, pero es CRUDO. github.com/szjani/predaddy-issuetracker-sample/blob/3.0/src/hu/… Le preguntaré al autor cuál es su opinión, él sabe más sobre DDD que yo.
inf3rno
1
El mío es que debes usar múltiples comandos en una "unidad de trabajo" si quieres consistencia inmediata. Si está hablando de una coherencia eventual, la pregunta no tiene sentido para mí. Otra posible solución para enviar un CompositeCommand que puede contener los Comandos que desea ejecutar atómica. Puede ser una colección simple, lo único que importa es que el autobús pueda manejarlo adecuadamente.
inf3rno
1
Según él, debe intentar lograr una relación 1: 1 entre los comandos y las solicitudes HTTP. Si no puede hacerlo (es un mal olor), entonces debe usar el volumen (lo llamé compuesto) para hacerlo atómico.
inf3rno

Respuestas:

11

Creo que puede tener un proceso de usuario para la implementación de la discordancia aquí.

Primero: ¿un usuario honestamente querrá realizar múltiples cambios en un archivo simultáneamente? Un cambio de nombre (que puede incluir o no un cambio de ruta), un cambio de propiedad y tal vez un cambio en el contenido del archivo (en aras de la discusión) parecen acciones separadas.

Tomemos el caso donde la respuesta es "sí" - sus usuarios realmente quieren hacer estos cambios simultáneamente.

En ese caso, yo recomiendo fuertemente contra cualquier aplicación que envía múltiples eventos - RenameFileCommand, MoveFileCommand, ChangeOwnerCommand- para representar esta única intención del usuario.

¿Por qué? Porque los eventos pueden fallar. Tal vez sea extremadamente raro, pero su usuario envió una operación que parecía atómica: si falla uno de los eventos posteriores, el estado de su aplicación ahora no es válido.

También está invitando a los riesgos de carrera en un recurso que se comparte claramente entre cada uno de los controladores de eventos. Deberá escribir "ChangeOwnerCommand" de tal manera que el nombre y la ruta del archivo no importen, ya que podrían estar desactualizados para cuando se reciba el comando.

Al implementar un sistema de reposo no controlado por eventos con mover y renombrar archivos, prefiero garantizar la coherencia mediante el uso de algo como un sistema eTag: asegúrese de que la versión del recurso que se está editando es la versión que el usuario recuperó por última vez, y fallará si ha sido modificado desde entonces. Pero si está enviando múltiples comandos para esta operación de usuario único, necesitará incrementar su versión de recurso después de cada comando, por lo que no tiene forma de saber que el recurso que el usuario está editando realmente es la misma versión que el recurso que leyeron por última vez .

Lo que quiero decir con eso es: ¿qué pasa si alguien más realiza otra operación en el archivo casi al mismo tiempo? Los 6 comandos podrían acumularse en cualquier orden. Si solo tuviéramos 2 comandos atómicos, el comando anterior podría tener éxito y el comando posterior podría fallar "el recurso se ha modificado desde la última vez que se recuperó". Pero no hay protección contra esto cuando los comandos no son atómicos, por lo que se viola la coherencia del sistema.

Curiosamente, hay un movimiento hacia algo como la arquitectura basada en eventos en REST, llamada "Descanse sin poner", recomendada en el radar de tecnología de Thoughtworks, enero de 2015 . Hay un blog considerablemente más largo sobre Descanso sin poner aquí .

Esencialmente, la idea es que POST, PUT, DELETE y GET están bien para aplicaciones pequeñas, pero cuando necesita comenzar a suponer cómo interpretar put y post y delete en el otro extremo, introduce el acoplamiento. (p. ej., "cuando ELIMINO el recurso asociado con mi cuenta bancaria, la cuenta debería cerrarse") Y la solución propuesta es tratar REST de una manera más basada en eventos. es decir, PUBLICAR la intención del usuario como un recurso de evento único.

El otro caso es más simple. Si sus usuarios no quieren hacer todas esas operaciones simultáneamente, no las deje. PUBLICA un evento para cada intento del usuario. Ahora puede usar el control de versiones etag en sus recursos.

En cuanto a las otras aplicaciones que están utilizando una API muy diferente a sus recursos. Eso huele a problemas. ¿Puedes construir una fachada de la antigua API sobre tu API RESTful y apuntarla a la fachada? es decir, exponer un servicio que realiza múltiples actualizaciones a un archivo en secuencia a través del servidor REST?

Si no construye la interfaz RESTful sobre la solución anterior, ni construye una fachada de la interfaz anterior sobre la solución REST, e intenta mantener ambas API apuntando a un recurso de datos compartido, experimentará grandes dolores de cabeza.

perfeccionista
fuente
Puedo ver la falta de coincidencia, sin embargo, por REST, en principio, es posible actualizar el estado de múltiples campos PONIENDO el nuevo estado en el recurso (por alguna representación). Así es como REST funciona por definición: transferencia de estado de representación, lo malo es que la intención del usuario se pierde en el proceso. Además, REST es casi un estándar para API externa. Simplemente quiero diseñar la aplicación para poder usar ambas. Eliminar PUT es como una solución alternativa debido a ES. Por lo que puedo ver, un comando de actualización que emite múltiples eventos es factible siempre que el manejo de comandos y eventos esté en una transacción.
pelirroja
Etag es algo que me gustaría hacer de todos modos. Además, su enlace a "publicación de blog más larga" es el mismo que el primer enlace.
pelirroja
Volveré a esto después de considerarlo con más pensamientos sobre PUT. Ciertamente, eliminar PUT no es solo "una solución para ES". He arreglado el enlace del blog.
perfeccionista
4

Justo ahora me encontré con el siguiente artículo, que alienta a especificar los nombres de los comandos en la solicitud al servidor en el encabezado Content-Type (mientras sigue los 5 niveles de tipo de medios).

En el artículo, mencionan que el estilo RPC es malo para REST y sugieren extender Content-Type para especificar el nombre del comando:

Un enfoque común es utilizar recursos de estilo RPC, por ejemplo / api / InventoryItem / {id} / rename. Si bien esto aparentemente elimina la necesidad de verbos arbitrarios, va en contra de la presentación orientada a los recursos de REST. Debemos recordar que un recurso es un sustantivo y el verbo HTTP es el verbo / acción y los mensajes autodescriptivos (uno de los principios de REST) ​​son el vehículo para transmitir otros ejes de información e intención. De hecho, el comando en la carga útil del mensaje HTTP debería ser suficiente para expresar cualquier acción arbitraria. Sin embargo, confiar en el cuerpo del mensaje tiene sus propios problemas, ya que el cuerpo generalmente se entrega como una secuencia y amortiguar el cuerpo en su totalidad antes de identificar la acción no siempre es posible ni sabio.

PUT /api/InventoryItem/4454c398-2fbb-4215-b986-fb7b54b62ac5 HTTP/1.1
Accept:application/json, text/plain, */*
Accept-Encoding:gzip,deflate,sdch
Content-Type:application/json;domain-model=RenameInventoryItemCommand`

El artículo está aquí: http://www.infoq.com/articles/rest-api-on-cqrs

Puede leer más sobre 5 niveles de tipo de medios aquí: http://byterot.blogspot.co.uk/2012/12/5-levels-of-media-type-rest-csds.html


Aunque están exponiendo los eventos de dominio a la API REST, lo que consideraría una mala práctica, me gusta la solución, ya que no crea un nuevo "protocolo" únicamente para CQRS, ya sea enviando nombres de comandos en el cuerpo o en extra encabezado, y se mantiene fiel a los principios RESTful.

pelirrojo
fuente