¿Cómo manejar los efectos secundarios en CRQS al reproducir eventos?

10

Se dice que en CQRS es fácil corregir un error, simplemente se vuelve a implementar y luego se reproducen los eventos.

Pero, ¿qué pasaría si uno de los eventos causara que un sistema externo que no esté bajo su control "envíe un artículo" al cliente si solo repite los eventos, el artículo se enviará dos veces?

¿Cómo resuelves eso?

Jas
fuente

Respuestas:

6

Debe hacer una separación clara entre los eventos que modifican el estado de su modelo de lectura y los eventos (potencialmente) que modifican el estado de los sistemas externos. Asegúrese de no tener ningún "evento mixto" que modifique ambos estados juntos. De esa manera, puede reproducir sus eventos en un "modo de reproducción" específico donde esos eventos para el sistema externo no se vuelven a activar. En este modo, también "simula" cualquier evento que haya sido iniciado originalmente por el sistema externo (ahora los toma de la cola de reproducción).

No olvide que el paso de redespliegue significa en realidad restablecer el estado del modelo de lectura a un punto anterior en el tiempo. Probablemente esto no sea nada que pueda hacer (o deba hacer) para el estado de los sistemas externos.

Doc Brown
fuente
"No se olvide, el paso de redespliegue significa en realidad restablecer el estado del modelo de lectura a un punto anterior en el tiempo. Esto probablemente no sea nada que pueda hacer (o deba hacer) para el estado de los sistemas externos". -> pero ¿qué pasa si quiero que mi repetición vuelva a intentar llamadas externas fallidas del sistema como envío? en este caso, mi nueva implementación de una repetición no solo restablecería el estado del modelo de lectura sino que también causaría eventos externos, ¿esto suena correcto o me falta algo?
Jas
2
@Jas: no desea abusar de la "repetición" para volver a intentar una llamada fallida del sistema externo. Utiliza la "repetición" para obtener el modelo de lectura de tu propio sistema en el mismo estado que tenía antes. Eso significa que en caso de una solicitud de envío fallida, su sistema fue informado antes acerca de esta falla y almacenó esa información en algún lugar de su estado. La reproducción se asegura de que esta información siga ahí después de "volver a implementar y reproducir". Entonces, después de la reproducción, su sistema podría aplicar una estrategia de "reintento de envío en caso de falla" (que no tiene nada que ver con CQRS, cualquier sistema de pedidos robusto debería tener una estrategia de este tipo).
Doc Brown
Interesante, esto es lo que tenía en mente hacer, solo me preguntaba si hay un "patrón" en esto para no reinventar la rueda.
Jas
3

Del artículo de búsqueda de eventos de Martin Fowler :

La idea fundamental de Event Sourcing es garantizar que cada cambio en el estado de una aplicación se capture en un objeto de evento, y que estos objetos de evento se almacenen en la secuencia en que se aplicaron durante la misma vida útil que el estado de la aplicación.

Por lo tanto, cuando necesite restaurar el estado de su sistema a un determinado momento, vuelva a reproducir el estado almacenado , no los controladores de eventos, hasta ese momento.

Dicho esto, si solo está trabajando con datos de estado, no debería haber ningún efecto en el sistema externo. A menos que tenga activadores o observadores en su tienda de eventos, en cuyo caso debe deshabilitarlos mientras dure la restauración. Dado que usted dice que no tiene control sobre el sistema externo, no debería haber ningún intento de restaurar su estado utilizando la API expuesta, ya que no sabe qué efectos secundarios puede tener en su sistema. Si la restauración coloca el sistema en un estado intermedio (por ejemplo, debido a operaciones fallidas en el sistema externo), esto no debería caer dentro de las responsabilidades de una repetición de eventos.

devnull
fuente
2

Pero, ¿qué pasaría si uno de los eventos causara que un sistema externo que no esté bajo su control "envíe un artículo" al cliente si solo repite los eventos, el artículo se enviará dos veces?

Para elegir un ejemplo específico, consideremos cómo podría funcionar un enfoque "al menos una vez" de los efectos secundarios.

State currentState = State.InitialState
for(Event e : events) {
    currentState = currentState.apply(e)
}
for(SideEffect s : currentState.querySideEffects()) {
    performSideEffect(s)

Entonces, el modelo de dominio rastrea lo que hay que hacer; pero deja el hacer real a la aplicación

En el contexto de ejecutar un comando, la idea básica se ve igual. Los efectos secundarios reales ocurren fuera de la transacción que actualiza el modelo.

Entonces, las pruebas unitarias para su modelo podrían verse más o menos así

{
    // Given
    State currentState = State.InitialState

    // When
    Events events = List.of(OrderPlaced)

    // Then
    List.of(SendEmail) === currentState.applyAll(events).querySideEffects()
}

{
    // Given
    State currentState = State.InitialState

    // When
    Events events = List.of(OrderPlaced, EmailSent)

    // Then
    List.EMPTY === currentState.applyAll(events).querySideEffects()
}

Los puntos principales aquí son

  • La actualización del modelo no tiene efectos secundarios; Los efectos secundarios reales ocurren fuera de la transacción que actualiza el modelo.
  • Un evento que describe el resultado del efecto secundario debe volver.
VoiceOfUnreason
fuente