Persistenz mit Event Sourcing

Nicht den aktuellen Zustand eines (Teil-)Systems abzuspeichern, sondern stattdessen die Historie aller Zustandsänderungen zu verzeichnen – das ist der Kern des Event-Sourcing-Prinzips. Es verspricht flexible und wartbare Software, die keine Daten "vergisst". Das muss weder komplex noch unperformant sein und eröffnet neue Perspektiven im Umgang mit Daten.

In Pocket speichern vorlesen Druckansicht 1 Kommentar lesen
Lesezeit: 16 Min.
Von
  • Philip Jander
Inhaltsverzeichnis

Nicht den aktuellen Zustand eines (Teil-)Systems abzuspeichern, sondern stattdessen die Historie aller Zustandsänderungen zu verzeichnen – das ist der Kern des Event-Sourcing-Prinzips. Es verspricht flexible und wartbare Software, die keine Daten "vergisst". Das muss weder komplex noch unperformant sein und eröffnet neue Perspektiven im Umgang mit Daten.

Welche dieser SQL-Anweisungen löscht Daten: INSERT, UPDATE, SELECT, DELETE? Klar: DELETE, aber war es das schon? Nein, denn auch ein UPDATE löscht immer Daten: nämlich genau die Felder, die aktualisiert wurden. Auch der Grund für eine solche Zustandsänderung ist danach nicht mehr ohne weiteres rekonstruierbar. Der Zugriff auf diese Informationen kann jedoch durchaus wieder relevant werden – sei es zur Nachvollziehbarkeit im Rahmen eines Audits, zur Fehlerkorrektur oder zur Umsetzung neuer Anforderungen, die einen Bezug zu vergangenen Zuständen erfordern.

Event-Sourcing-Systeme erfassen die Ursache jeder Zustandsänderung in einem Ereignis. Das ist eine Datenstruktur, deren Typ den fachlichen Grund einer Zustandsänderung beschreibt und deren Felder die mit dem Ereignis verbundenen Daten enthalten. Die Aufzeichnungen des Systems bestehen nun ausschließlich aus der Liste aller aufgetretenen Ereignisse (siehe den folgenden Pseudocode). Dabei lassen sich zum einen zu dieser Historie Ereignisse nur hinzufügen, zum anderen einmal verzeichnete Ereignisse nicht löschen oder modifizieren. Gespeichert werden die Ereignisse entweder generisch serialisiert oder in fachspezifischer Form strukturiert. Ersteres wird vor allem bei Line-of-Business-Anwendungen genutzt, um die Umsetzung von Änderungen zu erleichtern.

Kunde_fügte_Produkt_zu_Warenkorb_hinzu 
[ KundeId = 789, WarenkorbId = 12931, ProduktId = 55, Menge = 1,
Zeitpunkt = 20121217.163809, ... ]

Kunde_fügte_Produkt_zu_Warenkorb_hinzu
[ KundeId = 789, WarenkorbId = 12931, ProduktId = 61, Menge = 2,
Zeitpunkt = 20121217.164534, ... ]

Kunde_entfernte_Produkt_aus_Warenkorb
[ KundeId = 789, WarenkorbId = 12931, ProduktId = 61, Menge = 1,
Zeitpunkt = 20121217.164540, ... ]

Kunde_entfernte_Produkt_aus_Warenkorb
[ KundeId = 789, WarenkorbId = 12931, ProduktId = 55, Menge = 1,
Zeitpunkt = 20121217.165008, ... ]

Kunde_schloss_Warenkorb_mit_Bestellung_ab
[ KundeId = 789, WarenkorbId = 12931, Zeitpunkt = 20121217.165050, ... ]

Benötigt man einen Wert des aktuellen Systemzustands, ist er nun aus der Vorgeschichte des Systems zu berechnen, da der aktuelle Zustand nicht explizit vorgehalten wird (Abb. 1).

Zustand als Projektion aus Ereignissen (Abb. 1)

Hierzu durchläuft das System in einer einfachen Schleife alle relevanten Ereignisse der Historie. Auf einen Akkumulator wendet man für jedes Ereignis die diesem entsprechende Modifikation an.

Entwicklung von Zuständen durch Ereignisse (Abb. 2)

Das Ergebnis (Abb. 2) wird dann zurückgeliefert (wieder Pseudocode):

def Aktueller_Warenkorb(historie)

var warenkorb = new Warenkorb()

historie.foreach (event) ->

| Kunde_fügte_Produkt_zu_Warenkorb_hinzu =>
warenkorb.Produkte[event.ProduktId] += event.Menge
invarianten_pruefen(warenkorb)

| Kunde_entfernte_Produkt_aus_Warenkorb =>
warenkorb.Produkte[event.ProduktId] -= event.Menge
invarianten_pruefen(warenkorb)

| Kunde_schloss_Warenkorb_mit_Bestellung_ab =>
warenkorb.Abgeschlossen = true
break

end

return warenkorb

end

Mathematisch gesehen ist diese Berechnung für jeden benötigten Zustandswert nichts weiter als eine Projektion. Alle Information entstammen den Ereignissen, sodass jeder Aufruf der Projektion mit dem gleichen Ereignislog immer den gleichen Zustandswert berechnet. Auch vergangene Zustände sind leicht rekonstruierbar. Hierzu ist die Projektion lediglich bei einem bestimmten Zeitpunkt abzubrechen. Darüber hinaus lassen sich in gewissen Grenzen auch Zustände bestimmen, die beim Design des Systems gar nicht vorgesehen waren.