September 2015

Band 30, Nummer 9

Innovation – Event-Sourcing für allgemeine Anwendungen

Von Dino Esposito | September 2015

Dino EspositoEs ist eine solch gängige und natürliche Aktivität, dass Sie sich nicht einmal viel Gedanken darum machen. Wenn Sie sich die Speicherung von Daten vorstellen, denken Sie von selbst an ein Format, das einfach den aktuellen Status der Daten bewahrt. Wenngleich es verschiedene sehr große Systeme gibt, z. B. bei Banken und Versicherungen, in denen sämtliche Softwareaktionen sorgfältig protokolliert und aufgezeichnet werden, ist das Speichern des aktuellen Datenstatus für die meisten Anwendungen und Websites mehr als ausreichend.

Beim Ansatz mit dem aktuellen Status wird eine Momentaufnahme des Systemstatus erstellt, die dauerhaft gespeichert wird. Die Daten befinden sich in der Regel in einer relationalen Datenbank. Das reicht aus, um neue Transaktionen durchzuführen und die Ergebnisse vergangener Transaktionen abzurufen. Szenarien, in denen die Speicherung des aktuellen Status unzureichend ist, waren im letzten Jahrzehnt eher selten.

Doch heutzutage ändert sich die Geschäftswelt schnell. Das Nachverfolgen von Geschäfts- und Geschäftsbereichsereignissen wird immer mehr zur Notwendigkeit. Event-Sourcing (ES) ist ein Muster, das sich auf die Speicherarchitektur und die Art und Weise auswirkt, in der Sie gespeicherte Daten abrufen und einfügen. Bei ES geht es nicht nur um das Überwachen und Aufzeichnen unternehmensrelevanter Ereignisse in einem beständigen Bereich. Es geht vor allem um die Verwendung einer niedrigeren Abstraktionsebene zum Speichern Ihrer Daten und Verwenden von Ad-hoc-Tools und Mustern zum Erstellen mehrerer Datenprojektionen.

ES mag wie eine intelligente und coole Möglichkeit zur Protokollierung und Überwachung von Geschäftsfunktionen aussehen. Es handelt sich aber eigentlich um eine neue Speichermodelltheorie, die so relevant wie das relationale Modell seit seinen Anfängen ist. ES hat möglicherweise sogar eine größere Auswirkung auf moderne Software als NoSQL-Speicher. ES ist keine Alternative zu heutigen relationalen und NoSQL-Produkten. ES wird relationalen Datenbanken und NoSQL-Datenspeichern vorgelagert implementiert. Bei ES geht es um das Verändern Ihrer Vorstellung von Anwendungsspeicher und das Verwenden von Ereignissen anstelle von statusbehafteten Werten als Datentoken.

Wann ist Event-Sourcing hilfreich?

Eine grundlegende, aber gängige Möglichkeit über das Speichern des aktuellen Status hinaus ist das Nachverfolgen des Aktualisierungsverlaufs. Stellen Sie sich eine einfache Buchhandlungsanwendung vor. Für jedes Buch gibt es eine Beschreibungseigenschaft, und Sie erteilen Ihren Benutzern Bearbeitungsberechtigungen. Wollen Sie eine alte Beschreibung nachverfolgen, wenn ein Benutzer eine neue eingibt?

Die jeweiligen Anforderungen können unterschiedlich sein, aber stellen Sie sich vor, dass das Nachverfolgen von Aktualisierungen bei diesem Beispiel wichtig ist. Wie können Sie dies umsetzen? Eine Option ist das Speichern des aktuellen Buchstatus und Protokollieren sämtlicher Aktualisierungsdetails in einer gesonderten Tabelle. Sie können über einen Datensatz für jede Aktualisierung verfügen. Der Aktualisierungsdatensatz enthält dabei das Delta der Aktualisierung, so z. B. den alten und neuen Wert jeder geänderten Spalte.

Hierzu haben Sie auch eine andere Möglichkeit. Die Tabelle "Books" kann mehrere Einträge für dasselbe Buch enthalten, die mit einer angegebenen ID markiert sind. Jeder Datensatz stellt dabei einen Status mit Zeitstempel in sortierter Reihenfolge dar (siehe Abbildung 1).

Mehrere Datensätze enthalten den Verlauf einer Entität
Abbildung 1: Mehrere Datensätze enthalten den Verlauf einer Entität

Dieses Szenario erfordert eine Ad-hoc-API zum Lesen den aktuellen Datensatzstatus. Es ist nicht einfach eine Abfrage im Repository, über die der Datensatz anhand der ID ausgewählt wird. Sie müssen denjenigen mit dem spätesten Zeitstempel oder der höchsten aktualisierten zunehmenden Nummer wählen. Darüber hinaus bildet die Vereinigung aller Ereignisse im Zusammenhang mit einer gegebenen Datenentität einen Datenstrom. Dieser Ergebnisdatenstrom ist ein beliebtes Konzept in ES.

ES hilft, wenn es geschäftlich erforderlich ist, eine Folge von Ereignissen nachzuverfolgen. Wenngleich ES an Querschnittsthemen wie Protokollierung oder Überprüfung erinnert, ist das Prinzip unterschiedlich. ES protokolliert keine Ereignisse, um ein Profil zu erstellen oder Ausnahmen nachzuverfolgen. ES verfolgt lediglich Geschäftsereignisse nach. Und es handelt sich nicht um ein Querschnittsthema, sondern um eine architekturbezogene Entscheidung, die primär für Speicher gilt.

Definition von Event-Sourcing

Kurz gesagt, geht es beim ES um das Verwenden von Ereignissen als primäre Datenquelle. ES ist für beliebige Anwendungen nicht notwendigerweise nützlich, weshalb dieses Konzept jahrzehntelang meist ignoriert wurde. Falls ES heute nutzlos scheint, liegt dies vor allem daran, dass Sie es noch nicht benötigt haben.

Ich möchte die Notwendigkeit von ES wie folgt zusammenfassen: Wenn ein Geschäftsbereichsexperte die Folge der Ereignisse nachverfolgen muss, die die Software generieren kann, ist Event-Sourcing eine geeignete Möglichkeit. Andernfalls kann es sein, dass Ereignisse nach wie vor nützlich sind, um Workflows auszudrücken und Teile der Geschäftslogik zu verketten. In diesem Fall sind Ereignisse jedoch keine wesentlichen Bestandteile des Geschäftsbereichs und werden ggf. nicht dauerhaft gespeichert. Dies ist das heute noch gängige Szenario.

Lassen Sie uns nun ansehen, was Sie tun, wenn Ereignisse die primäre Datenquelle Ihrer Anwendung sind. ES wirkt sich auf zwei Aspekte der Speicherung aus: Dauerhaftigkeit und Abfragen. Dauerhaftigkeit lässt sich durch drei wichtige Vorgänge charakterisieren: Einfügen, Aktualisieren und Löschen. In einem ES-Szenario ist das Einfügen fast wie bei einem klassischen System, das den aktuellen Entitätsstatus dauerhaft speichert. Das System erhält eine Anforderung und schreibt ein neues Ereignis in den Speicher. Das Ereignis enthält einen eindeutigen Bezeichner (z. B. eine GUID), Typnamen oder Code, der den Typ des Ereignisses, einen Zeitstempel und zugehörige Informationen bestimmt.

Der Aktualisierungsvorgang besteht aus einem anderen Einfügevorgang in denselben Container mit Datenentitäten. Der neue Eintrag gibt einfach die Daten wieder, d. h. welche Eigenschaften sich geändert haben, den neuen Wert und, sofern im Geschäftsbereich relevant, warum und wie sie sich geändert haben. Sobald eine Aktualisierung erfolgt ist, entwickelt sich der Datenspeicher weiter (siehe Abbildung 2).

Ein neuer Datensatz kennzeichnet eine Aktualisierung an der Entität mit der ID 1
Abbildung 2: Ein neuer Datensatz kennzeichnet eine Aktualisierung an der Entität mit der ID 1

Der Löschvorgang funktioniert auf die gleiche Weise wie eine Aktualisierung, weist aber unterschiedliche Informationen auf, damit klar ist, dass es sich um einen Löschvorgang gehandelt hat.

Beim Vornehmen von Aktualisierungen auf diese Weise stellen sich einige Fragen, was Abfragen angeht. Woher wissen Sie, ob ein bestimmter Datensatz vorhanden ist oder was sein aktueller Status ggf. ist? Dies erfordert eine Ad-hoc-Ebene für Abfragen zum konzeptuellen Auswählen aller Datensätze mit einer übereinstimmenden ID und das anschließende Analysieren des Dataset-Ereignisses nach dem Ereignis.

Es könnte beispielsweise eine neue Datenentität basierend auf dem Inhalt des Ereignisses erstellt werden. Diese würde anschließend alle nachfolgenden Schritte wiedergeben und zurückgeben, was am Ende des Datenstroms übrig bleibt. Diese Methode wird als Ereigniswiedergabe bezeichnet. Die einfache Wiedergabe von Ereignissen zum Neuerstellen des Status kann Bedenken hinsichtlich Leistung hervorrufen.

Denken Sie an ein Bankkonto. Bei einem Kunden, der vor Jahren ein Bankkonto eröffnet hat, haben sich seitdem Hunderte von Vorgängen und Ereignissen angesammelt. Um den derzeitigen Kontostand zu erhalten, müssen Sie mehrere hundert Vorgänge wiedergeben, um den Status des aktuellen Kontos wiederherzustellen. Dies ist möglicherweise nicht immer durchführbar.

Es gibt Hilfslösungen für dieses Szenario. Die wichtigste sieht das Erstellen von Momentaufnahmen vor. Eine Momentaufnahme ist ein Datensatz, der den bekannten Status der Entität zu einem bestimmten Zeitpunkt speichert. Auf diese Weise entfällt die Notwendigkeit, Ereignisse wiederzugeben, die zeitlich vor den Momentaufnahmen liegen.

ES ist an keine Technologien oder Produkte gebunden, sei es eine bestimmte relationale Datenbank oder ein NoSQL-Datenspeicher. Doch ES erfordert mindestens eine besondere Softwarekomponente: den Ereignisspeicher. Der Ereignisspeicher ist im Wesentlichen ein Ereignisprotokoll. Sie können einen solchen Speicher mit Ihrem eigenen Code auf der Grundlage einer beliebigen Datenspeicher-API Ihrer Wahl erstellen.

Ein Ereignisspeicher hat zwei wesentliche Merkmale. Es handelt sich um Datenspeicher, der nur Anfügevorgänge zulässt. Er unterstützt keine Aktualisierung und kann optional nur bestimmte Typen von Löschvorgängen unterstützen. Zweitens muss ein Ereignisspeicher den Datenstrom der Ereignisse im Zusammenhang mit einem angegebenen Schlüssel zurückgeben können. Sie können diese Codeebene selbst erstellen oder verfügbare Tools und Frameworks nutzen.

Optionen für den Ereignisspeicher

Sie können einen Ereignisspeicher mit allen Instrumenten implementieren, die funktionieren. Häufig wird eine relationale Datenbank oder eine Art NoSQL-Datenspeicher als Modul für die Dauerhaftigkeit von Daten verwendet. Wenn Sie mit einer relationalen Datenbank arbeiten möchten, können Sie über eine Tabelle pro Entität verfügen, die eine Zeile pro Ereignis generiert.

Ereignisse haben in der Regel unterschiedliche Layouts. Jedes Ereignis hat möglicherweise eine andere Anzahl zu speichernder Eigenschaften, wodurch es schwierig ist, ein gemeinsames Schema für alle Zeilen zu erarbeiten. Wenn ein gemeinsames Schema als Ergebnis der Vereinigung aller möglichen Spalten sogar möglich ist und eine akzeptable Leistung bietet, ist es einfach zu implementieren.

Andernfalls können Sie das Feature "Spalteindexspeicher" von SQL Server 2014 prüfen, das die Tabelle zum Speichern von Daten in vertikalen Spalten statt horizontalen Zeilen konfiguriert. Eine weitere Option, die mit jeder Version von SQL Server funktioniert, ist das Normalisieren von Ereigniseigenschaften zu einem JSON-Objekt, das als Zeichenfolge in einer einzelnen Spalte gespeichert wird.

Im NoSQL-Jargon ist ein "Dokument" ein Objekt mit einer variablen Anzahl von Eigenschaften. Einige NoSQL-Produkte sind auf das Speichern von Dokumenten spezialisiert. Aus der Perspektive eines Entwicklers könnte es nicht einfacher sein. Erstellen Sie eine Klasse, füllen Sie sie mit Werten, und speichern Sie sie, wie sie ist. Der Typ der Klasse ist die wesentliche Informationen, die mehrere Ereignisse verknüpft. Wenn Sie sich an NoSQL halten, haben Sie nur ein Ereignisobjekt, das Sie speichern müssen.

Laufende Projekte

ES ist ein vergleichsweise junger Architekturansatz. Standardtools zum Schreiben von Code auf Grundlage ereignisbasierter Datenspeicher befinden sich noch in der Entwicklung. Sie können auf jeden Fall eine eigene ES-Lösung definieren, doch einige Ad-hoc-Tools können Ihnen helfen, an die Speicherung von Ereignissen strukturierter heranzugehen.

Der Hauptvorteil eines Datenspeichers mit Unterstützung von Ereignissen ist, dass das Tool, z. B. eine Datenbank, dafür sorgt, dass Lese- und Anfügeereignisse nur auf eine Weise erfolgen, die die unternehmensweite Einheitlichkeit des Even-Sourcing-Ansatzes sicherstellt. Ein Framework, das speziell auf das Speichern von Ereignissen ausgelegt ist, ist das NEventStore-Projekt (neventstore.org). Es ermöglicht Ihnen das Schreiben und Zurücklesen von Ereignissen und wird von der Dauerhaftigkeit unabhängig betrieben. So speichern Sie ein Ereignis:

var store = Wireup.Init()
  .UsingSqlPersistence("connection")
  .InitializeStorageEngine()
  .UsingJsonSerialization()
  .Build();
var stream = store.CreateStream(aggregateId);
stream.Add(new EventMessage { Body = eventToSave });
stream.CommitChanges(aggregateId);

Um Ereignisse zurückzulesen, öffnen Sie den Datenstrom, und durchlaufen Sie die Auflistung von Ereignissen mit ausgeführtem Commit in einer Schleife.

Event Store (geteventstore.com) ist eine weitere Lösung, die durch das Anbieten einer API für einfache HTTP- und .NET-Ereignisdatenströme funktioniert. Im ES-Jargon entspricht ein Aggregat einem Datenstrom im Speicher. Sie können drei grundlegende Vorgänge auf einen Ereignisdatenstrom anwenden: Schreiben von Ereignissen, Lesen des letzten Ereignisses, eines bestimmtes Ereignis und sogar eines Segments von Ereignissen und Abonnieren zum Abrufen von Aktualisierungen.

Es gibt drei Arten von Abonnements. Eines ist flüchtig, was heißt, dass beim Schreiben eines Ereignisses in einen angegebenen Datenstrom jedes Mal eine Rückruffunktion aufgerufen wird. Ein weiterer Vorteil ist, dass Sie Benachrichtigungen für jedes Ereignis im Speicher ab einem bestimmten Ereignis und danach für jedes neu hinzugefügte Ereignis erhalten. Schließlich gibt es das dauerhafte Abonnement für das Szenario, bei dem mehrere Consumer auf zu verarbeitende Ereignisse warten. Das Abonnement stellt sicher, dass Ereignisse für Consumer mindestens einmal, aber möglicherweise mehrmals und in unvorhersehbarer Reihenfolge übermittelt werden.

Zusammenfassung

Event-Sourcing nutzt Ereignisse als Anwendungsdatenquelle. Doch Sie entwerfen die Anwendung nicht so, dass der letzte bekannte Status von Entitäten gespeichert wird, sondern die Liste relevanter Geschäftsereignisse. Die Ereignisdatenquelle speichert Daten auf einer niedrigen Ebene der Abstraktion. Sie müssen Projektionen anwenden, um vor dort zum tatsächlichen Entitätsstatus zu gelangen, der für Transaktionen und Abfragen erforderlich ist. Die Projektion ist der Prozess des Wiedergebens von Ereignissen und Ausführens verschiedener Aufgaben. Die offensichtlichste Projektion ist das Erstellen des aktuellen Status. Doch anhand von Ereignissen sind beliebige viele Anzahlen und Typen von Projektionen möglich.


Dino Esposito ist Mitautor von "Microsoft .NET: Architecting Applications for the Enterprise" (Microsoft Press, 2014) und "Programming ASP.NET MVC 5" (Microsoft Press, 2014). Esposito ist Technical Evangelist für die Microsoft .NET Framework- und Android-Plattformen bei JetBrains und spricht häufig auf Branchenveranstaltungen weltweit. Auf software2cents.wordpress.com und auf Twitter unter twitter.com/despos lässt er uns wissen, welche Softwarevision er verfolgt.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Jon Arne Saeteras