Was ist Event Sourcing und wie funktioniert es?
Durch die immer größeren Datenmengen kommen klassische SQL Datenbanksysteme bei Onlineplattformen, IoT Anwendungen und vielen weiteren modernen Softwarebeispielen beim Schreiben an ihre Grenzen. Dies liegt mitunter an dem Transaktion nach dem CRUD Prinzip. (CRUD = Create, Read, Update, Delete / Erstellen, Lesen, Aktualisieren, Löschen)
Nachfolgende Liste zeigt einige Probleme und Einschränkungen die bei CRUD entstehen können:
- Durch die Bearbeitung von Einträgen in einer Datenbanktabelle kann es bei vielen Nutzer die einen Schreibvorgang ausführen zu Konflikten und Performanceproblemen kommen.
- Eine Verteilung auf mehrere Instanzen kann zu Inkonsistenzen führen, was die Skalierbarkeit einschränkt.
- Der Verlauf der Änderungen ist nicht mehr nachvollziehbar.
- Das Zurücksetzen von bereits geschehenen Änderungen ist nicht möglich.
- Transaktionen über mehrere Datenbanken, z.B. bei verschiedenen Microservices, können nicht konsistent sein. (2-Generals Problem)
Die angeführten Probleme lassen sich allerdings mit einer anderen Denkweise bei der Datenspeicherung – dem Event Sourcing – lösen. Um das Konzept des “Event Sourcing” erläutern zu können, betrachten wir zuerst die Eigenschaften eines Events:
- Events sind in der Vergangenheit vorgefallen und deshalb auch entsprechend zu formulieren. Zum Beispiel: account was charged.
- Events sind auf Grund der Eigenschaft, dass sie in der Vergangenheit passiert sind, nicht mehr veränderbar. Sie können jedoch die Effekte eines vorhergegangenen Events aufheben oder verändern.
- Events haben genau einen Erzeuger, dem sie zugeordnet werden können.
- Events können noch zusätzliche Informationen über sich enthalten.
Mit dem Beispiel einer Kontoverwaltung kann das Konzept des Event Sourcing mit dem Speichern der Daten in Tabelle eines herkömmlichen RDBMS (Rationalen-Datenbank-Management-System) verglichen werden. Das Speichern von Informationen in einem RDBMS kann wie in der nachfolgenden Abbildung stattfinden. Dabei wird der Befehl Buche 50 von Konto mit der ID 152 ab in einen SQL Befehl umgewandelt. Der Befehl adressiert dabei die Tabelle accounts. Der Wert in der Spalte balance in der Zeile der ID: 152 wird dann von 200 um 50 auf 150 verringert. Dies führt dazu, dass der Verlauf der Änderungen des accounts nicht mehr nachvollzogen werden kann.
Beim Event-Sourcing hingegen wird, wie in der nächsten Abbildung gezeigt, der aktuelle Status der Applikation über die zuvor definierten Events dargestellt. Dabei wird zuerst der Befehl AdjustBalance mit dem Wert -50 in das Event BalanceAdjusted umgewandelt. Der Zahlenwert adjust: -50 definiert die Änderung des Kontostandes. Des Weiteren muss eine Sequenznummer eventSequenceId: 4 vorhanden sein, um die genaue Reihenfolge der Events wiederherstellen zu können. Diese darf für das jeweilige Konto nur genau einmal vergeben werden. Dadurch können alle Änderungen des Kontostandes zu jedem Zeitpunkt der Vergangenheit nachvollzogen und wiederhergestellt werden.
Das oben beschriebene Event-Sourcing Beispiel haben wir mit Scala und akka-typed implementiert und auf github zur Verfügung gestellt: https://github.com/innFactory/akka-event-sourcing-example
Das Beispiel enthält dabei ein Aktorsystem. In diesem Aktorensystem erstellt wiederum ein RootAktor einen AccountSupervisor Aktor und einen CommandBot Aktor. Der AccountSupervisor erstellt wiederum die Aktoren der einzelnen Accounts die die einzelnen Konten darstellen. Der CommandBot sendet eine zufällige Anzahl von Befehlen an die Accounts. Die Befehle werden zu Events verarbeitet und in der Event History des jeweiligen Aktors abgespeichert. Der Zustand des Aktors, oder anders gesagt der aktuelle Kontostand ändert sich den Events entsprechend.
In einer echten Anwendung werden die Events nicht in den Aktoren selbst, sondern in einer Datenbank als Journal abgespeichert. Dazu eignen sich besonders No-SQL-Datenbanken wie Cassandra, DynamoDB oder der Google Datastore. Dadurch kann der Zustand des Aktors auch nach einem Fehler oder gar nach einem Absturz wiederhergestellt werden. Selbst wenn Abhängigkeiten zu anderen Systemen bestehen wird das system einen konsistenten Zustand erlangen (= eventually consistent). Der Wiederherstellungsprozess kann beschleunigt werden indem zusätzlich zu den Events im Journal auch Snapshots von “Zuständen” abgelegt werden. Das bedeutet, dass der Aktor beim Wiederherstellen einen Anfangszustand über einen Snapshot erhält und nur die Events verarbeitet, die nach diesem Snapshot angefallen sind.
Da beim Event Sourcing nur neue Daten in Form von Events geschrieben werden und keine alten Daten verändert oder gelöscht werden, können die Events auf eine Vielzahl von Servern verteilt werden. Ein Nachteil ist allerdings die Leseseite. Dieses Problem lässt sich beim Event Sourcing mithilfe von Command-Query-Responsibility-Segregation und separaten Read-Views lösen. Häufig liest man Event Sourcing in Verbindung mit CQRS. Es ist aber durchaus auch möglich nur Event Sourcing einzusetzen. Für Java bzw. Scala Anwendungen existiert für diese Art von Architektur das Framework akka-persistence.
Im zweiten Teil lesen Sie wie sie die Leseseite unseres Beispiels durch CQRS beschleunigen.