Como faço para lidar com efeitos colaterais no Event Sourcing?


14

Vamos supor que queremos implementar um pequeno subsistema de segurança para um aplicativo financeiro que avise os usuários por email se um padrão estranho for detectado. Para este exemplo, o padrão consistirá em três transações como as representadas. O subsistema de segurança pode ler eventos do sistema principal de uma fila.

O que eu gostaria de receber é um alerta que é uma conseqüência direta dos eventos que acontecem no sistema, sem uma representação intermediária que modela o estado atual do padrão.

  1. Monitoramento ativado
  2. Transação processada
  3. Transação processada
  4. Transação processada
  5. Alerta acionado (id: 123)
  6. E-mail para alerta enviado (para o ID: 123)
  7. Transação processada

Tendo isso em mente, achei que a fonte de eventos poderia se aplicar aqui muito bem, embora eu tenha uma pergunta sem uma resposta clara. O alerta acionado no exemplo tem um efeito colateral claro, um email precisa ser enviado, uma circunstância que deve acontecer apenas uma vez. Portanto, isso não deve acontecer ao reproduzir todos os eventos de um agregado.

Até certo ponto, vejo o email que precisa ser enviado semelhante às materializações geradas pelo lado da consulta que eu já vi tantas vezes na literatura de fornecimento de CQRS / Event, mas com uma diferença não tão sutil.

Nesta literatura, o lado da consulta é construído a partir de manipuladores de eventos que podem gerar uma materialização do estado em um determinado ponto, lendo novamente todos os eventos. Nesse caso, porém, isso não pode ser realizado exatamente assim pelas razões explicadas anteriormente. A idéia de que todo estado é transitório não se aplica tão bem aqui . Precisamos registrar o fato de que um alerta foi enviado para algum lugar.

Uma solução fácil para mim seria ter uma tabela ou estrutura diferente, na qual você mantém registros dos alertas que foram acionados anteriormente. Como temos um ID, poderíamos verificar se um alerta com o mesmo ID foi emitido antes. Ter essas informações tornaria o SendAlertCommand idempotente. Vários comandos podem ser emitidos, mas o efeito colateral só ocorrerá uma vez.

Mesmo tendo essa solução em mente, não sei se é uma dica de que há algo errado com essa arquitetura para esse problema.

  • Minha abordagem está correta?
  • Existe algum lugar onde eu possa encontrar mais informações sobre isso?

É estranho que eu não tenha conseguido encontrar mais informações sobre isso. Talvez eu esteja usando o texto errado.

Muito obrigado!

Respostas:


12

Como faço para lidar com efeitos colaterais no Event Sourcing?

Versão curta: o modelo de domínio não realiza efeitos colaterais. Ele os rastreia . Os efeitos colaterais são realizados usando uma porta que se conecta ao limite; quando o email é enviado, você envia a confirmação de volta ao modelo de domínio.

Isso significa que o email é enviado para fora da transação que atualiza o fluxo de eventos.

Precisamente onde, fora, é uma questão de gosto.

Então, conceitualmente, você tem um fluxo de eventos como

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

E a partir desse fluxo, você pode criar uma dobra

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

A dobra informa quais e-mails não foram confirmados, então você os envia novamente:

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

Efetivamente, essa é uma confirmação de duas fases: você está modificando o SMTP no mundo real e atualizando o modelo.

O padrão acima fornece um modelo de entrega pelo menos uma vez. Se você quiser no máximo uma vez, pode mudar

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

Há uma barreira de transação entre tornar o EmailPrepared durável e realmente enviar o email. Há também uma barreira de transação entre enviar o email e tornar o EmailDelivered durável.

As Mensagens Confiáveis ​​de Udi Dahan com Transações Distribuídas podem ser um bom ponto de partida.


2

Você precisa separar 'Eventos de mudança de estado' de 'Ações'

Um Evento de Alteração de Estado é um evento que altera o estado do objeto. Estes são os que você armazena e reproduz.

Uma ação é algo que o objeto faz com outras coisas. Eles não são armazenados como parte do Event Sourcing.

Uma maneira de fazer isso é com manipuladores de eventos, que você conecta ou não, dependendo se deseja executar as Ações.

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

Agora, no meu serviço de monitoramento, posso ter

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
}

Se você precisar registrar os emails enviados, poderá fazê-lo como parte do SendAlarmEmail. Mas eles não são eventos no significado de Event Sourcing

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.