Eventbasierte Architekturen
Bevor wir über ereignisbasierte Architekturen sprechen können, müssen wir erst einmal klären, was ein Event beziehungsweise ein Domain Event ist.
Nach Martin Fowler gibt es vier Muster, die wir in eventbasierten Architekturen finden können:
Event Notification: Eine Komponente kommuniziert über Events, dass etwas geschehen ist. Dazu wird in der Regel ein Mediator verwendet, der Sender und Empfänger voneinander entkoppelt. In der Praxis kann das zum Beispiel ein Event Bus oder eine Message Queue sein.
Ein sehr gutes Beispiel für Event Notification ist das klassische Logging in einer Anwendung, beispielsweise "ich habe eine Datenbankverbindung geöffnet". Oder auch eine Benutzerinteraktion in einer Bedienoberfläche oder Website, die von einem Analysesystem wie Google Analytics weiterverarbeitet wird. (Wie genau ... wer weiß das schon).
Event-Carried State Transfer: Wenn Ereignisse einen Empfänger lediglich darüber informieren, dass etwas passiert ist, ist der Empfänger gezwungen, den Sender nach dem aktuellen Zustand zu fragen. Das macht den Empfänger nicht nur langsam, sondern auch direkt zur Laufzeit voneinander abhängig: Wenn der Sender nicht antwortet, kann der Empfänger nicht weiterarbeiten.
Event-Carried State Transfer bedeutet, dass Ereignisse genügend Informationen enthalten, so dass der Empfänger ohne Rückfrage beim Sender agieren kann. Dies macht die Systemkomponenten unabhängiger voneinander, allerdings führt dies zu Eventual Consistency im Gesamtsystem.
Beim Event Sourcing werden Veränderungen - wie in der richtigen Welt - als Folge von Ereignissen gespeichert. Wir sprechen von einem Event Log oder einem Event Ledger. Dieses Verfahren ist seit dem späten Mittelalter bekannt und erprobt und als ordnungsgemäße Buchführung in Unternehmen sogar gesetzlich verankert.
Luca Pacioli, der Mathematiklehrer von Leonardo da Vinci, beschrieb in seinem Buch "Summa de Arithmetica, Geometria, Proportioni et Proportionalità" die doppelte Buchführung, wie sie damals beispielsweise von den Medici verwendet wurde. Hierbei werden zunächst einmal alle Buchungen in zeitlicher Folge in einem Journal festgehalten. Im Journal dürfen keine Streichungen oder Veränderungen vorgenommen werden. Ein Journal ist also unveränderlich und append only, weil es kontinuierlich nach unten wächst.
Um den aktuellen Systemzustand zu ermitteln, gehen wir durch die gespeicherten Events und wenden deren Auswirkungen auf den Systemzustand an. In der Finanzbuchhaltung würden wir ausgehend von einem initialen Kontostand, der im einfachsten Fall einfach Null ist, jeden Zahlungseingang zum aktuellen Kontostand addieren und jeden Zahlungsausgang subtrahieren. Die entsprechenden Events können beispielsweise "Zahlung erhalten" und "Abbuchung ausgeführt" heißen.
Haben wir auf diese Weise alle Buchungen (Events) verarbeitet, haben wir den aktuellen Kontostand, der nachweisbar richtig ist, weil wir ihn anhand von unveränderlichen Events berechnet haben. Wir können diese Berechnung jederzeit wiederholen.
Technisch gesprochen haben wir hier ein ausführbares Audit Log, da wir den Systemzustand direkt aus diesem Audit Log rekonstruieren können.
Die klassische Speicherung des Systemzustands in einer relationalen Datenbank kann dabei entfallen, wenn wir die Events alle Informationen enthalten, die wir benötigen, um den Systemzustand wiederherzustellen.
CQRS (Command Query Responsibility Segregation) erweitert das CQS-Prinzip von Bertrand Meyer. Hierbei teilen wir die Verantwortlichkeit für schreibende (Commands) und lesende (Queries) Zugriffe in einer Anwendung auf und schaffen unterschiedliche Modelle für die Verarbeitung von Kommandos beziehungsweise Queries. Insbesondere haben wir damit die Möglichkeit, für unterschiedliche Queries ein jeweils separates Modell zu erstellen, das optimal auf die jeweilige Query abgestimmt ist.
Was scheinbar mehr Komplexität bedeutet, weil wir mehrere Modelle ímplementieren müssen, ist in Wirklichkeit eine Vereinfachung, denn jedes einzelne Modell fällt deutlich einfacher aus. Durch dieses Vorgehen werden zukünftige Änderungen an der Software deutlich einfacher und risikoärmer, weil jedes Modell nur einen einzigen fachlichen Use Case hat. Auf diese Weise setzen wir elegant das Single Responsiblity-Prinzip um.