Architektur

Event Sourcing – eine funktionale Betrachtung

Data Science Karlsruhe

Entwurfsmuster kennt eigentlich jeder. Wenn man Programmieren lernt, wird man bereits früh mit ihnen konfrontiert. Die bewährten Lösungsansätze verschiedener Entwurfsprobleme werden wieder und wieder angewendet und bieten einem Ansatz ein gewisses Gerüst. Durch den wiederkehrenden Ansatz erkennt auch jemand, der nicht direkt von Anfang an in einen Entwurf involviert ist, welche Muster eingesetzt wurden und kann daraus die Probleme, die zu lösen versucht wurden, ableiten. Populäre Beispiele sind abstract factory, facade und observer Entwurfsmuster.

Diese Art von Schablonen wird jedoch nicht nur in Entwürfen eingesetzt. Weitere Mustertypen sind beispielsweise Architekturmuster, wie Model-View-Controller, oder Analysemuster.

Analysemuster bieten, genau wie Entwurfsmuster, eine bewährte, wiederverwendbare Vorlage für ein Problem. Hierbei geht es jedoch um ein Problem aus dem Bereich der Anwendungsdomäne. Denn bevor Entwurfsmuster angewendet werden können, müssen die Zusammenhänge des Bereichs, den man durch Software digitalisieren will, modelliert werden.

In diesem Bereich hatte ich die Möglichkeit, das Event Sourcing Muster genauer kennenzulernen. Es ist eines von zahlreichen Analysemustern, die Martin Fowler veröffentlicht hat. Dieses Muster möchte ich an dieser Stelle genauer vorstellen.

Event Sourcing bedeutet kurz gesagt, dass jede Änderung eines Datensatzes als eigener Datensatz gespeichert wird. Somit wird der Zustand eines Objekts indirekt gespeichert und kann jederzeit anhand des Verlaufs wiederhergestellt werden.

Wann sollte man Event Sourcing einsetzen?

Ob Entwurfsmuster oder Analysemuster, grundsätzlich sollten Muster nur eingesetzt werden, wenn auch ein Problem oder eine Anforderung zu Grunde liegt, die gelöst werden muss. Liegt eine Anforderung vor, die das Speichern des gesamten zeitlichen Verlaufs von Zuständen oder Objekten benötigt, ist Event Sourcing ein geeignetes Muster.

Ein solches Szenario ist, wenn sicherheitskritische Daten verändert werden, beispielsweise bei Buchungen auf einem Konto. Doch nicht jeder Einsatz von Event Sourcing muss sicherheitskritisch sein. So kann bei geteilter Nutzung von Daten für Nutzer auch interessant sein, den Änderungsverlauf einzusehen.

Git ist ein Beispiel, das Event Sourcing anhand der Domänen Programmcode und Dateiinhalten einsetzt und für Entwickler leicht nachvollziehbar ist. Die populäre verteilte Versionsverwaltung bietet dem Nutzer alle Vorteile des Musters als Feature an. Git stellt dem Nutzer den zeitlichen Verlauf der Änderungen an Dateien zur Verfügung. In diesem Zeitstrahl kann gesprungen werden, es kann also der Zustand zu einem früheren Zeitpunkt wiederhergestellt werden. Der Zeitstrahl kann aufgespalten und wieder zusammengeführt werden.

Eine funktionale Sicht auf Event Sourcing

Anhand des Beispiels Git hat man nun schon eine Vorstellung, wie Event Sourcing funktioniert. Doch diese Vorstellung möchte ich nun mit einer formalen Beschreibung des Musters konkretisieren. Dabei habe ich eine funktionale Sichtweise auf das Muster gewählt, da das Muster dadurch stark formalisiert wird und durch die funktionale Denkweise neue Gesichtspunkte im Vergleich zur klassischen Beschreibung in den Vordergrund rücken. Die funktionale Beschreibung orientiert sich an der Typendarstellung von Haskell und mathematischen Funktionen.

Event Sourcing basiert darauf, dass jede Zustandsänderung durch ein Event-Objekt ausgelöst wird. Im Beispiel Git entspricht so ein Event einem Commit. Zur Umsetzung von Event Sourcing wird eine Liste aller Events, wie dem Commit-Log in Git, und eine Zustandsübergangsfunktion benötigt.

Funktional beschrieben bedeutet das:

(e:es) :: [Event] ist das Log, beziehungsweise eine Liste der Events. Ist das erste Event in der Liste und  ist eine Liste mit allen weiteren Events der Liste.

Diese Funktion ist die Zustandsübergangsfunktion. Tritt ein neues Event auf, wird  mit dem Event und dem aktuellen Zustand des Objekts oder der Anwendung als Eingabeparameter aufgerufen. Der Zustand der Anwendung zu jedem beliebigen Zeitpunkt kann anhand der Zustandsübergangsfunktion und der Event Liste ermittelt werden.

Die Ermittlung des Anwendungszustand zu einem bestimmten Zeitpunkt kann auch durch eine Funktion verdeutlicht werden:

Die Funktion  erhält als Eingabeparameter zum einen eine Funktion, die dem Typ der Funktion f entspricht. Hier kann eine beliebige Zustandsübergangsfunktion als Parameter übergeben. Als weiteren Parameter bekommt die Funktion das Event-Log und einen initialen Objekt- oder Anwendungszustand.

Die Funktion arbeitet dann die Liste rekursiv durch, bis nur noch ein Element in der Liste ist. Nun wird vom letzten zum ersten Element der Liste aus dem initialen Zustand der nächste Zustand ermittelt.

Überträgt man diese Betrachtung auf das Beispiel Git, so ist ein Event ein Commit, die Eventliste das Commit-Log und die Zustandsübergangsfunktion erzeugt aus dem vorherigen Zustand einer Datei und den Änderungen des Commits die neue Datei mit den Änderungen des Commits. Baut man nun die Datei vom ersten Commit an wieder auf, wird vom initialen Commit an jede Änderung am Dateiinhalt erneut auf die Datei ausgeführt, bis man schlussendlich den aktuellen Inhalt der Datei erhält.

Herausforderungen

Das Analysemuster kommt nicht ohne Herausforderungen und Einschränkungen. Ein Teil dieser Herausforderungen lassen sich klar aus der funktionalen Beschreibung des Musters ableiten:

Funktionen beziehungsweise funktionale Programmierung gilt als Seiteneffektfrei. Dadurch ist die Ermittlung des Zustandes der Anwendung zu einem bestimmten Zeitpunkt deterministisch. Das Muster wird jedoch in den meisten Fällen eben nicht durch mathematische Funktionen, sondern durch Methoden, die durchaus Seiteneffekte haben können, in einer Programmiersprache wie beispielsweise Java, umgesetzt. Die Anwendungen, die das Muster umsetzen, sind oft Teil eines verteilten Systems und kommunizieren mit anderen Anwendungen. Die Zustandsübergangsfunktion löst somit teilweise andere Events aus und kommuniziert mit anderen Anwendungen. Dadurch haben diese Methoden möglicherweise starke Seiteneffekte und genau die sind eine große Herausforderung des Musters. Dadurch wird die Umsetzung programmierintensiv und komplex. Außerdem wird durch das Persistieren aller Event-Objekte deutlich mehr Speicher verbraucht. Dies hat heute jedoch wenig Relevanz, da Festplattenspeicher immer günstiger wird.

Die Umsetzung von Event Sourcing steht und fällt somit mit der Umsetzung der Zustandsübergangsfunktion. Neben möglichen Seiteneffekten birgt diese noch weitere Schwierigkeiten: Enthält die Zustandsübergangsfunktion Fehler oder muss weiterentwickelt werden, entspricht sie nicht mehr ihrer ursprünglichen. Dadurch kann sich das Ergebnis der Funktion trotz gleicher Eingabeparameter verändern und somit auch das Ergebnis beim Wiederherstellen alter oder neuer Zustände.

Fazit

Bei der ausführlichen Betrachtung des Analysemusters werden die Vor- und Nachteile des Musters deutlich. Auf der einen Seite bekommt man die vollständige Nachvollziehbarkeit aller Änderungen des Objekt- oder Anwendungszustandes und kann zu jedem beliebigen Zeitpunkt springen. Auf der anderen Seite bringt es in der Umsetzung einen hohen Programmieraufwand mit sich und benötigt eine hohe Sorgfalt in der Umsetzung der Zustandsübergangsfunktion.

Der Einsatz des Musters sollte also gut durchdacht sein und sollte sich wirklich auf die Informationen beschränken, bei denen der zeitliche Verlauf, die Wiederherstellbarkeit und die Nachvollziehbarkeit wichtiger als die einfache Weiterentwicklung sind.

Charlotte Proeller war Werkstudentin bei der esentri AG

Dieser Text stammt von Charlotte Pröller, Studentin am Karlsruher Institut für Technologie. Charlotte schrieb ihre Bachelorarbeit über das Thema „Entwicklung und prototypische Implementierung einer Vorgehensweise zur Wiederherstellung von Zuständen in eventgetriebenen verteilten Systemen“ bei esentri und wurde dabei von mir betreut.

Was wir noch so in Sachen Architektur bieten?

Hier erfahren