Talky talky or
chatty chatty?
Exploring Digital Future. Together.
08.08.2024

Event Sourcing

Find
Technology
Business Application Development

Stell dir vor, du verwaltest eine Banking-App. Bei der Analyse der Logs bemerkst du, dass ein Benutzer 2000 Überweisungen in einer einzigen Session veranlasst hat. Da ist irgendetwas faul, denkst du dir. Wurde das System gehackt? Oder gibt es einen Fehler in der Logik, in der Überweisungen verarbeitet werden? Vielleicht ein Edge Case, der dazu führt, dass mehrere Transaktionen durch eine einzige Benutzeraktion ausgelöst werden? Um wie viel Geld ging es bei diesen Überweisungen? Was genau ist im System vor, während und nach diesen auffälligen Ereignissen passiert? Du hast Zeitdruck, da du diese Analyse schnell und präzise durchführen musst, um weitere Schäden zu vermeiden. Zum Glück basiert deine Systemarchitektur auf Event Sourcing. In diesem Artikel erklären wir dir, was sich hinter diesem Konzept verbirgt und wieso es in puncto Auditfähigkeit und Nachvollziehbarkeit ein absoluter Gamechanger sein kann. 

Grundlegende Terminologie

Um es mit dem Vokabular eines Softwarearchitekten auszudrücken: Event Sourcing ist ein Design Pattern. Die grundlegende Idee ist, dass alle Änderungen am Zustand einer Anwendung als Ereignisse aufgezeichnet werden. Mit Event Sourcing werden alle Aktionen im System, die Daten verändern, gespeichert. Damit kann die Datenhistorie jederzeit rekonstruiert und nachvollzogen werden. Dies sorgt für Transparenz und erleichtert die gründliche Analyse von Auffälligkeiten im System.  Im eingangs erwähnten Fall können wir damit bspw. den plötzlichen Anstieg von Überweisungen bis ins kleinste Detail analysieren. Welche Bestandteile sich hinter dem Gesamtkonzept von Event Sourcing verbergen, erklären wir dir in diesem Artikel. 

Jede Operation, die Daten verändert, ist ein Event im Kontext von Event Sourcing. Beispiele für solche Events sind "Überweisung initiiert", "Einzahlung vorgenommen" oder "Geld abgehoben". Diese Events werden durch schwarze Post-its in der untenstehenden Visualisierung dargestellt:

Event Sourcing 1

Event Store Database

Alle Events werden in einer sogenannten Event Store Database gespeichert. Wenn sich mehrere Events auf ein bestimmtes Objekt beziehen, gehören diese zu einem Stream. Falls du die Events in einem Stream analysieren willst, kannst du ein Event Replay anstoßen. Hier werden die Events erneut “abgespielt” und du kannst nachvollziehen, welche Änderungen durch die Events erfolgt sind. Jede Update-Operation auf deine Daten wird als neues Ereignis an das Ende des Streams angehängt. Dadurch wird sichergestellt, dass die Datenhistorie erhalten bleibt, ohne dass ein Snapshot von jedem Zwischenstand erstellt wird. Wenn ein Update mehrere Streams betrifft, müssen diese Streams alle zeitnah aktualisiert werden. Das Thema Datenkonsistenz – verschiedene Streams auch bei hoher Systemauslastung aktuell zu halten – kann eine größere Herausforderung bei der Implementierung von Event Sourcing darstellen. Mehr dazu im Abschnitt zu Eventual Consistency.

Im Beispiel der Banking-App müssen wichtige Metriken wie Kontostand und Transaktionsverlauf ständig aktualisiert werden, um ordnungsgemäße Bankgeschäfte zu gewährleisten. Allgemein ausgedrückt: Wir müssen schnelle Leseoperationen sicherstellen und gleichzeitig die vollständige Datenhistorie aufrechterhalten. Dies kann durch die Verwendung von Projektionen erreicht werden, die einen schnellen Zugriff auf den aktuellen Stand der Daten im Stream ermöglichen. Jede Projektion stellt eine Sicht auf die Daten dar, die dem Benutzer mehr oder weniger direkt angezeigt wird. Der Stream, aus dem die Projektionen erzeugt werden, enthält weiterhin die vollständige Aufzeichnung früherer Events, die zum aktuellen Zustand der Daten geführt haben.

Wenn ein Kunde in unserer Banking-App eine Überweisung veranlasst, löst diese Aktion einen Schreibvorgang im Event Stream aus. Gleichzeitig wird eine Update-Operation in der Projektion “Überweisungen”  ausgeführt, um sicherzustellen, dass der Kontostand die Transaktion korrekt wiedergibt. Dies ist in der nachstehenden Abbildung zu sehen:

Event Sourcing 2

CQRS und Eventual Consistency

Wir haben vorigen Beispiel ein weiteres Konzept verwendet, das oft zusammen mit Event Sourcing implementiert wird. Das Design Pattern Command Query Responsibility Segregation (CQRS) wird verwendet, um zu vermeiden, dass sich Schreib- und Lesevorgänge gegenseitig blockieren. Dies bedeutet, dass der Datenstand nicht bei jedem Schreibvorgang in einer synchronen Operation aktualisiert wird. Stattdessen löst jeder Befehl eine Update-Anforderung für die vom Benutzer gelesenen Projektionen aus. Diese Update-Anforderungen können asynchron verarbeitet werden. Dadurch werden Wartezeiten minimiert und die Daten werden trotzdem zeitnah aktualisiert – eine “best of both worlds” Lösung.

Dieses Konzept einer leichten Verzögerung zwischen einem Schreibvorgang und der Aktualisierung der Projektion wird als Eventual Consistency bezeichnet. Je nach Anwendungsfall kann ein einziger Schreibvorgang Aktualisierungen in mehreren Projektionen auslösen.

Wenn zum Beispiel ein Kunde eine Überweisung veranlasst, kann diese Aktion zu Aktualisierungen in verschiedenen Projektionen führen. Es könnte zu einer Anpassung in der Projektion “Kontostand”  führen, da die Mittel abgezogen werden, während gleichzeitig die Projektion “Transaktionshistorie” aktualisiert wird, um die Überweisung widerzuspiegeln.

Dadurch wird sichergestellt, dass alle relevanten Bestandteile der Banking-App konsistent, wenn auch mit einer leichten Verzögerung, aktualisiert werden. Dieser Prozess ist in diesem Diagramm zu sehen:

Event Sourcing 3

Zusammenfassung

In der Architektur unserer Banking-App werden alle Ereignisse im System als Events in der Event Store Database gespeichert.

Alle Events, die sich auf ein bestimmtes Objekt beziehen, werden in einem Stream gruppiert. Schreibvorgänge innerhalb eines Streams lösen Aktualisierungen von Projektionen aus. Die Daten werden gemäß dem Design Pattern Command Query Responsibility Segregation (CQRS) verarbeitet. Dabei wird die Bearbeitung von Schreiboperationen (Commands) von Leseoperationen (Queries) getrennt, was die Performance erheblich verbessert. Durch die Implementierung von CQRS können wir jeden Bestandteil des Systems entsprechend seinen spezifischen Anforderungen unabhängig analysieren, optimieren und skalieren.

Unsere Events sind alle in einem Stream gelandet, der nicht geändert werden kann und sich daher ideal für die Analyse eignet. Jetzt können wir die auffälligen Events untersuchen, die wir eingangs im Artikel erwähnt haben. In diesem Fall wurde der Benutzer, der 2000 Transaktionen veranlasste, als Hacker identifiziert. Mit Hilfe der in diesem Artikel beschriebenen Konzepte konnten wir jedoch sein Konto sperren, bevor er sich selbst Geld überweisen konnte. Event Sourcing auf die 1!