¿Cómo trato los efectos secundarios en Event Sourcing?

14

Supongamos que queremos implementar un pequeño subsistema de seguridad para una aplicación financiera que advierte a los usuarios por correo electrónico si se detecta un patrón extraño. Para este ejemplo, el patrón consistirá en tres transacciones como las representadas. El subsistema de seguridad puede leer eventos desde el sistema principal desde una cola.

Lo que me gustaría recibir es una alerta que es una consecuencia directa de los eventos que suceden en el sistema, sin una representación intermedia que modele el estado actual del patrón.

  1. Monitoreo activado
  2. Transacción procesada
  3. Transacción procesada
  4. Transacción procesada
  5. Alerta activada (id: 123)
  6. Correo electrónico para alerta enviada (para id: 123)
  7. Transacción procesada

Teniendo esto en cuenta, pensé que el abastecimiento de eventos podría aplicarse muy bien aquí, aunque tengo una pregunta sin una respuesta clara. La alerta activada en el ejemplo tiene un efecto secundario claro, se debe enviar un correo electrónico, una circunstancia que solo debería ocurrir una vez. Por lo tanto, no debería suceder cuando se reproducen todos los eventos de un agregado.

Hasta cierto punto, veo el correo electrónico que debe enviarse de manera similar a las materializaciones generadas por el lado de la consulta que he visto tantas veces en la literatura de abastecimiento de CQRS / Evento, con una diferencia no tan sutil.

En esta literatura, el lado de la consulta está construido a partir de controladores de eventos que pueden generar una materialización del estado en un punto dado, leyendo nuevamente todos los eventos. En este caso, sin embargo, eso no se puede lograr exactamente así por las razones explicadas anteriormente. La idea de que cada estado es transitorio no se aplica tan bien aquí . Necesitamos registrar el hecho de que se envió una alerta a alguna parte.

Una solución fácil para mí sería tener una tabla o estructura diferente donde guardar registros de las alertas que se activaron previamente. Como tenemos una identificación, podríamos verificar si antes se emitió una alerta con la misma identificación. Tener esta información haría que SendAlertCommand sea idempotente. Se pueden emitir varios comandos, pero el efecto secundario solo ocurrirá una vez.

Incluso teniendo en cuenta esa solución, no sé si esto es una pista de que hay algo mal con esta arquitectura para este problema.

  • ¿Es correcto mi enfoque?
  • ¿Hay algún lugar donde pueda encontrar más información sobre esto?

Es extraño que no haya podido encontrar más información sobre esto. Tal vez he estado usando una redacción incorrecta.

Muchas gracias!

Jacob
fuente

Respuestas:

12

¿Cómo trato los efectos secundarios en Event Sourcing?

Versión corta: el modelo de dominio no realiza efectos secundarios. Los rastrea . Los efectos secundarios se realizan utilizando un puerto que se conecta al límite; cuando se envía el correo electrónico, devuelve el acuse de recibo al modelo de dominio.

Esto significa que el correo electrónico se envía fuera de la transacción que actualiza la secuencia del evento.

Precisamente donde, afuera, es cuestión de gustos.

Conceptualmente, tienes una secuencia de eventos como

EmailPrepared(id:123)
EmailPrepared(id:456)
EmailPrepared(id:789)
EmailDelivered(id:456)
EmailDelivered(id:789)

Y a partir de esta secuencia puedes crear un pliegue

{
    deliveredMail : [ 456, 789 ],
    undeliveredMail : [123]
}

El pliegue le indica qué correos electrónicos no han sido reconocidos, por lo que debe enviarlos nuevamente:

undeliveredMail.each ( mail -> {
    send(mail);
    dispatch( new EmailDelivered.from(mail) );
}     

Efectivamente, esta es una confirmación de dos fases: está modificando SMTP en el mundo real y luego está actualizando el modelo.

El patrón anterior le ofrece un modelo de entrega al menos una vez. Si quiere, como máximo, una vez, puede darle la vuelta

undeliveredMail.each ( mail -> {
    commit( new EmailDelivered.from(mail) );
    send(mail);
}     

Existe una barrera de transacción entre hacer que EmailPrepared sea duradero y realmente enviar el correo electrónico. También hay una barrera de transacción entre enviar el correo electrónico y hacer que EmailDelivered sea duradero.

La mensajería confiable de Udi Dahan con transacciones distribuidas puede ser un buen punto de partida.

VoiceOfUnreason
fuente
2

Debe separar 'Eventos de cambio de estado' de 'Acciones'

Un evento de cambio de estado es un evento que cambia el estado del objeto. Estos son los que almacena y reproduce.

Una acción es algo que el objeto hace a otras cosas. Estos no se almacenan como parte del Abastecimiento de eventos.

Una forma de hacerlo es con los gestores de eventos, que conecta o no dependiendo de si desea ejecutar las acciones.

public class Monitor
{
    public EventHander SoundAlarm;
    public void MonitorEvent(Event e)
    {
        this.eventcount ++;
        if(this.eventcount > 10)
        {
             this.state = "ALARM!";
             if(SoundAlarm != null) { SoundAlarm();}
        }
    }
}

Ahora en mi servicio de monitoreo puedo tener

public void MonitorServer()
{
    var m = new Monitor(events); //11 events
    //alarm has not been sounded because the event handler wasn't wired up
    //but the internal state is correctly set to "ALARM!"
    m.SoundAlarm += this.SendAlarmEmail;
    m.MonitorEvent(e); //email is sent
}

Si necesita registrar los correos electrónicos enviados, puede hacerlo como parte de SendAlarmEmail. Pero no son eventos en el sentido de Event Sourcing

Ewan
fuente