Come faccio a gestire gli effetti collaterali in Event Sourcing?

7

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 pattern. Per questo esempio, il modello consisterà in tre transazioni come quelle illustrate. Il sottosistema di sicurezza può leggere gli eventi dal sistema principale da una coda.

Quello che vorrei ottenere è un avviso che è una conseguenza diretta degli eventi che accadono 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

Avendo questo in mente, ho pensato che l'event sourcing potesse applicarsi molto bene, 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 verificarsi 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 query che ho visto tante volte nella documentazione di sourcing CQRS / Event, con una differenza non molto sottile però.

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

Una soluzione semplice per me sarebbe avere una tabella o una struttura diversa in cui tenere traccia degli avvisi che erano stati precedentemente attivati. Dato che abbiamo un ID, saremo in grado di verificare se prima è stato emesso un avviso con lo stesso ID. Avere questa informazione renderebbe identi- cente il SendAlertCommand. Diversi comandi possono essere emessi, ma l'effetto collaterale si verificherà solo una volta.

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

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

È strano che non sia stato in grado di trovare maggiori informazioni a riguardo. Forse ho usato la dicitura sbagliata.

Grazie mille!

    
posta IoChaos 24.07.2017 - 17:12
fonte

2 risposte

11

How do I deal with side effects in Event Sourcing?

Versione breve: il modello di dominio non esegue effetti collaterali. li registra . Gli effetti collaterali vengono eseguiti utilizzando una porta che si collega al confine; quando viene inviata l'e-mail, si invia la conferma al modello di dominio.

Ciò significa che l'email viene inviata al di fuori della transazione che aggiorna il file flusso di eventi.

Proprio dove, fuori, è una questione di gusti.

Quindi 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 stream puoi creare una piega

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

La piega ti dice quali email non sono state confermate, quindi le invii di nuovo:

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

In effetti, questo è un commit a due fasi: stai modificando SMTP nel mondo reale, e quindi stai aggiornando il modello.

Il modello sopra ti offre un modello di consegna almeno una volta. Se vuoi al massimo una volta, puoi capovolgerlo

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

C'è una barriera di transazione tra la realizzazione di EmailPrepared durevole e l'invio effettivo dell'e-mail. C'è anche una barriera di transazione tra l'invio dell'e-mail e la durata di EmailDelivered.

La messaggistica affidabile con le transazioni distribuite di Udi Dahan potrebbe essere un buon punto di partenza.

    
risposta data 24.07.2017 - 18:44
fonte
2

Devi separare gli "Eventi di modifica dello stato" da "Azioni"

Un evento di modifica dello stato è un evento che modifica lo stato dell'oggetto. Questi sono quelli che archivi e riproduci.

Un'azione è qualcosa che l'oggetto fa ad altre cose. Questi non sono memorizzati come parte di Event Sourcing.

Un modo per farlo è con i gestori di eventi, che puoi collegare o meno a seconda che tu voglia 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

    
risposta data 24.07.2017 - 18:31
fonte

Leggi altre domande sui tag