En CQRS / ES, ¿puede un comando crear otro comando?

10

En CQRS / ES, se envía un comando desde el cliente al servidor y se enruta al controlador de comandos apropiado. Ese controlador de comandos carga un agregado desde su repositorio, llama a algún método y lo guarda de nuevo en el repositorio. Se generan eventos. Un controlador de eventos / saga / administrador de procesos puede escuchar estos eventos para emitir comandos.

Entonces, los comandos (entrada) producen eventos (salida), que luego pueden retroalimentar al sistema más comandos (entrada). Ahora, ¿es una práctica común que un comando no emita ningún evento, sino que ponga en cola otro comando? Tal enfoque podría usarse para forzar la ejecución en un proceso externo.

EDITAR:

El caso de uso específico que tengo en mente es el procesamiento de los detalles de pago. El cliente envía un PayInvoicecomando, cuya carga incluye los detalles de la tarjeta de crédito del usuario. El PayInvoiceHandlerpasa un MakeInvoicePaymentcomando a un proceso separado, que es responsable de interactuar con la pasarela de pago. Si el pago se realiza correctamente, InvoicePaidse genera un evento. Si por alguna razón el sistema falla después de que el PayInvoicecomando persiste pero antes de que el MakeInvoicePaymentcomando persista, podemos rastrearlo manualmente (no habrá pasado ningún pago). Si el sistema falla después de que el MakeInvoicePaymentcomando persiste pero antes deInvoicePaidel evento persiste, es posible que tengamos una situación en la que se cargue a la tarjeta de crédito del usuario pero la factura no se marque como pagada. En ese caso, la situación tendría que ser investigada manualmente y la factura marcada manualmente como pagada.

Magnus
fuente

Respuestas:

11

En retrospectiva, creo que estaba complicando el problema.

En general, los comandos deben lanzar una excepción o generar uno o más eventos.

Si pudiera resumir la arquitectura de Event Sourcing sería la siguiente:

  • Los comandos son entradas que representan instrucciones para hacer algo.
  • Los eventos son salidas que representan hechos históricos de lo que se hizo.
  • Los controladores de eventos pueden escuchar eventos para emitir comandos, lo que ayuda a coordinar las diferentes partes del sistema.

Hacer que un comando cree otro comando causa ambigüedad en el significado general de los comandos y eventos: si un comando "dispara" otro comando, está implicando que un comando es "un hecho histórico de lo que se hizo". Esto contradice la intención de estos dos tipos de mensajes y podría convertirse en un camino resbaladizo, ya que otros desarrolladores podrían desencadenar eventos de eventos, lo que podría conducir a datos corruptos e inconsistencia eventual .

Con respecto al escenario específico que planteé, el factor de complicación fue que no quería que el hilo principal interactuara con la pasarela de pago, ya que (al ser un proceso persistente de un solo subproceso), esto no permitiría que se procesen otros comandos . La solución simple aquí es generar otro hilo / proceso para manejar el pago.

Para ilustrar, el cliente envía un PayInvoicecomando. El PayInvoiceHandlercomienza un nuevo proceso y lo pasa los datos de pago. El nuevo proceso se comunica con la pasarela de pago. Si el pago fue exitoso, llama invoice.markAsPaid()con el número de recibo (que produce el InvoicePaidevento). Si el pago no fue exitoso, llama invoice.paymentFailed()con pasa un código de referencia para una mayor investigación (que produce el InvoicePaymentFailedevento). Entonces, a pesar del hecho de que hay un proceso / hilo separado, el patrón permanece Command -> Event.

Con respecto a la falla, hay tres escenarios, el sistema podría fallar después de que el PayInvoicecomando persiste pero antes de que persistan los eventos InvoicePaido InvoicePaymentFailed. En este caso, no sabemos si se cargó la tarjeta de crédito del usuario. En tal caso, el usuario notará el cargo en su tarjeta de crédito y presentará una queja, en cuyo caso un miembro del personal puede investigar el problema y marcar manualmente la factura como pagada.

Magnus
fuente
1
Muchas gracias por la pregunta y la respuesta, buena comida para pensar. En general, todavía hay algunos puntos que me parecen confusos: (1) No he visto a muchas personas hablando sobre la noción de controladores de eventos. (2) Mi interpretación de su punto de vista traslada la mayor parte del trabajo a los controladores de eventos, hasta el punto en que el controlador de comandos se convierte en una función de traducción pura, lo que plantea la cuestión de si la separación en comandos / eventos es incluso necesaria. Vea esta pregunta de seguimiento si está interesado.
bluenote10
3

Obtendrá un sistema que está arquitectónicamente acoplado más libremente si solo emite eventos desde un comando. Dicho de otra manera, un comando no debería necesitar saber qué otros comandos externos emitir; esa debería ser la responsabilidad de la parte externa (quien debería suscribirse al evento y podría ser, como usted mencionó, un gerente de saga con responsabilidades de coordinación, o simplemente otro módulo que depende de esos eventos).

Erik Eidt
fuente
2

Visualización recomendada: Udi Dahan sobre mensajería confiable : no es exactamente lo que está describiendo, sino que está estrechamente relacionado.

Ahora, ¿es una práctica común que un comando no emita ningún evento, sino que ponga en cola otro comando?

No he visto a nadie recomendar esa práctica.

Respuesta corta: si no guarda algún estado, entonces no puede recuperar el comando en cola si se bloquea después de reconocer que lo ha recibido.

Si decide que necesita guardar el estado, no está claro que haya una gran ventaja en programar el segundo comando desde el primero, en lugar de usar un controlador de eventos.

VoiceOfUnreason
fuente