Come gestisco gli effetti collaterali in Event Sourcing?


14

Supponiamo di voler implementare un piccolo sottosistema di sicurezza per un'applicazione finanziaria che avvisa gli utenti via e-mail se viene rilevato uno strano schema. Per questo esempio, il modello consisterà in tre transazioni come quelle rappresentate. Il sottosistema di sicurezza può leggere gli eventi dal sistema principale da una coda.

Quello che vorrei ricevere è un avviso che è una conseguenza diretta degli eventi che si verificano nel sistema, senza una rappresentazione intermedia che modella lo stato corrente del modello.

  1. Monitoraggio attivato
  2. Transazione elaborata
  3. Transazione elaborata
  4. Transazione elaborata
  5. Avviso attivato (ID: 123)
  6. Email per avviso inviato (per ID: 123)
  7. Transazione elaborata

Tenendo presente questo, ho pensato che l'approvvigionamento di eventi potesse applicarsi molto bene qui, anche se ho una domanda senza una risposta chiara. L'avviso attivato nell'esempio ha un chiaro effetto collaterale, è necessario inviare un'e-mail, una circostanza che dovrebbe accadere solo una volta. Pertanto, non dovrebbe accadere quando si riproducono tutti gli eventi di un aggregato.

In una certa misura, vedo l'e-mail che deve essere inviata in modo simile alle materializzazioni generate dal lato della query che ho visto così tante volte nella letteratura sull'approvvigionamento CQRS / Eventi, con una differenza non così sottile.

In questa letteratura, il lato query è costruito da gestori di eventi che possono generare una materializzazione dello stato in un dato punto rileggendo nuovamente tutti gli eventi. In questo caso, tuttavia, ciò non può essere realizzato esattamente così per le ragioni spiegate in precedenza. L'idea che ogni stato sia transitorio non si applica così bene qui . Dobbiamo registrare il fatto che un avviso è stato inviato da qualche parte.

Una soluzione facile per me sarebbe avere una tabella o una struttura diversa in cui si conservano le registrazioni degli avvisi precedentemente attivati. Poiché disponiamo di un ID, saremmo in grado di verificare se in precedenza è stato emesso un avviso con lo stesso ID. Avere queste informazioni renderebbe SendAlertCommand idempotente. Diversi comandi possono essere emessi, ma l'effetto collaterale avverrà solo una volta.

Pur avendo in mente quella soluzione, non so se questo suggerisca che c'è qualcosa di sbagliato in questa architettura per questo problema.

  • Il mio approccio è corretto?
  • C'è un posto dove posso trovare maggiori informazioni su questo?

È strano che non sono stato in grado di trovare ulteriori informazioni al riguardo. Forse ho usato la formulazione sbagliata.

Grazie mille!

Risposte:


12

Come gestisco gli effetti collaterali in Event Sourcing?

Versione breve: il modello di dominio non esegue effetti collaterali. Si tiene traccia di loro. Gli effetti collaterali vengono eseguiti utilizzando una porta che si collega al confine; quando viene inviata l'e-mail, si rinvia il riconoscimento al modello di dominio.

Ciò significa che l'e-mail viene inviata al di fuori della transazione che aggiorna il flusso di eventi.

Proprio dove, fuori, è una questione di gusti.

Concettualmente, hai un flusso di eventi come

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

E da questo flusso puoi creare una piega

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

La piega ti dice quali e-mail non sono state riconosciute, quindi le rispedisci di nuovo:

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

In effetti, si tratta di un commit in due fasi: si sta modificando SMTP nel mondo reale e quindi si sta aggiornando il modello.

Il modello sopra ti dà un modello di consegna almeno una volta. Se lo desideri al massimo, puoi girarlo

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

Esiste una barriera transazionale tra rendere EmailPrepared durevole e inviare effettivamente l'e-mail. Esiste anche una barriera transazionale tra l'invio dell'e-mail e la durabilità di EmailDelivered.

La messaggistica affidabile di Udi Dahan con transazioni distribuite può essere un buon punto di partenza.


2

È necessario separare gli "Eventi di cambio di stato" da "Azioni"

Uno Stato Change Event è un evento che cambia lo stato dell'oggetto. Questi sono quelli che archivi e riproduci.

Un'azione è qualcosa che l'oggetto non ad altre cose. Questi non vengono archiviati come parte dell'evento.

Un modo per farlo è con i gestori di eventi, che si collegano o meno a seconda che si desideri eseguire le azioni.

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

Ora nel mio servizio di monitoraggio posso avere

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 è necessario registrare le e-mail inviate, è possibile farlo come parte di SendAlarmEmail. Ma non sono eventi nel senso di Event Sourcing

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.