Wie gehe ich mit Nebenwirkungen im Event Sourcing um?

14

Nehmen wir an, wir möchten ein kleines Sicherheitssubsystem für eine Finanzanwendung implementieren, das die Benutzer per E-Mail warnt, wenn ein seltsames Muster erkannt wird. In diesem Beispiel besteht das Muster aus drei Transaktionen, wie sie dargestellt sind. Das Sicherheitssubsystem kann Ereignisse aus dem Hauptsystem aus einer Warteschlange lesen.

Was ich erhalten möchte, ist eine Warnung, die eine direkte Folge der Ereignisse im System ist, ohne eine Zwischendarstellung, die den aktuellen Status des Musters modelliert.

  1. Überwachung aktiviert
  2. Transaktion verarbeitet
  3. Transaktion verarbeitet
  4. Transaktion verarbeitet
  5. Alarm ausgelöst (ID: 123)
  6. E-Mail für Benachrichtigung gesendet (für ID: 123)
  7. Transaktion verarbeitet

Vor diesem Hintergrund dachte ich, dass Event-Sourcing hier sehr gut angewendet werden könnte, obwohl ich eine Frage ohne klare Antwort habe. Die im Beispiel ausgelöste Warnung hat einen deutlichen Nebeneffekt. Es muss eine E-Mail gesendet werden. Dieser Umstand sollte nur einmal auftreten. Daher sollte es nicht passieren, wenn alle Ereignisse eines Aggregats wiedergegeben werden.

Bis zu einem gewissen Grad sehe ich die E-Mail, die gesendet werden muss, ähnlich den Materialisierungen, die von der Abfrageseite generiert wurden, die ich so oft in der CQRS / Event-Sourcing-Literatur gesehen habe, mit einem nicht so subtilen Unterschied.

In dieser Literatur besteht die Abfrageseite aus Ereignishandlern, die an einem bestimmten Punkt eine Materialisierung des Zustands erzeugen können, indem alle Ereignisse erneut gelesen werden. In diesem Fall kann dies jedoch aus den zuvor erläuterten Gründen nicht genau so erreicht werden. Die Vorstellung, dass jeder Zustand vorübergehend ist, trifft hier nicht so gut zu . Wir müssen die Tatsache aufzeichnen, dass irgendwo eine Warnung gesendet wurde.

Eine einfache Lösung für mich wäre eine andere Tabelle oder Struktur, in der Sie Aufzeichnungen über die zuvor ausgelösten Warnungen führen. Da wir eine ID haben, können wir überprüfen, ob zuvor eine Warnung mit derselben ID ausgegeben wurde. Wenn Sie diese Informationen haben, wird der SendAlertCommand idempotent. Es können mehrere Befehle ausgegeben werden, der Nebeneffekt tritt jedoch nur einmal auf.

Selbst wenn ich diese Lösung im Auge habe, weiß ich nicht, ob dies ein Hinweis darauf ist, dass mit dieser Architektur für dieses Problem etwas nicht stimmt.

  • Ist mein Ansatz korrekt?
  • Gibt es einen Ort, an dem ich weitere Informationen dazu finden kann?

Es ist seltsam, dass ich keine weiteren Informationen dazu finden konnte. Vielleicht habe ich den falschen Wortlaut verwendet.

Ich danke dir sehr!

Jakob
quelle

Antworten:

12

Wie gehe ich mit Nebenwirkungen im Event Sourcing um?

Kurzversion: Das Domänenmodell führt keine Nebenwirkungen aus. Es verfolgt sie. Nebenwirkungen werden über einen Port ausgeführt, der mit der Grenze verbunden ist. Wenn die E-Mail gesendet wird, senden Sie die Bestätigung an das Domänenmodell zurück.

Dies bedeutet, dass die E-Mail außerhalb der Transaktion gesendet wird, die den Ereignisstrom aktualisiert.

Genau dort, wo es draußen um Geschmack geht.

Konzeptionell haben Sie also einen Strom von Ereignissen wie

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

Und aus diesem Stream können Sie eine Falte erstellen

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

Die Falte zeigt an, welche E-Mails nicht bestätigt wurden, und Sie senden sie erneut:

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

Tatsächlich handelt es sich um ein zweiphasiges Commit: Sie ändern SMTP in der realen Welt und aktualisieren dann das Modell.

Das obige Muster gibt Ihnen ein mindestens einmaliges Liefermodell. Wenn Sie höchstens einmal möchten, können Sie es umdrehen

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

Es gibt eine Transaktionsbarriere zwischen der Dauerhaftigkeit von EmailPrepared und dem tatsächlichen Senden der E-Mail. Es gibt auch eine Transaktionsbarriere zwischen dem Senden der E-Mail und der Dauerhaftigkeit von EmailDelivered.

Udi Dahans zuverlässiges Messaging mit verteilten Transaktionen kann ein guter Ausgangspunkt sein.

VoiceOfUnreason
quelle
2

Sie müssen "Statusänderungsereignisse" von "Aktionen" trennen.

Ein Statusänderungsereignis ist ein Ereignis, das den Status des Objekts ändert. Dies sind diejenigen, die Sie speichern und wiedergeben.

Eine Aktion ist etwas, was das Objekt mit anderen Dingen macht. Diese werden nicht im Rahmen von Event Sourcing gespeichert.

Eine Möglichkeit hierfür sind Event-Hander, die Sie verkabeln oder nicht, je nachdem, ob Sie die Aktionen ausführen möchten.

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

Jetzt kann ich in meinem Überwachungsdienst haben

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
}

Wenn Sie die gesendeten E-Mails protokollieren müssen, können Sie dies im Rahmen von SendAlarmEmail tun. Es handelt sich jedoch nicht um Ereignisse im Sinne von Event Sourcing

Ewan
quelle