Inverse Mapping mit NHibernate

Event Sourcing

Test

Der Event-Store

Der Log, mit dem wir beim Eventsourcing arbeiten, ist ein Event-Store. Der Event-Store ist oft eine Datenbank, aber eigentlich ist das auch ziemlich egal. Der Event-Store hat die Aufgabe, Events in der Reihenfolge Ihres Auftretens zu speichern. Ein gutes Beispiel für einen Event-Store ist das Bankkonto. Nehmen wir einmal an, wir möchten ein Onlinebanking für eine Bank entwickeln. Für eine Bank ist es von Essenzieller Bedeutung, Millionen von Bankkonten leicht managen zu können. Es ist wichtig, dass der Kontostand leicht nachvollziehbar ist und im Notfall (der hoffentlich nicht eintritt) soll der Kontostand leicht korrigiert werden können. Im folgenden könnten für das Verwalten eines Bankkontos folgende Aktionen bentigt werden:

  • Bevor ein Konto verwendet wird, muss es eröffnet werden. Bei der Eröffnung eines Bankkontos wird der Kontoinhaber gespeichert. Nennen wir diese Aktion RegisterAccount. Das erfolgreiche Ausführen der Aktion führt zu dem Event AccountRegistered
  • Wenn das Konto eröffnet ist, können Einzahlungen getätigt werden. Für eine Einzahlung ist der Betrag relevant, der eingezahlt wird, die Währung und der EUR-Wechselkurs zum Zeitpunkt der Transaktion. Nennen wir die Aktion ProcessDeposit. Wird die Aktion erfolgreich ausgeführt, führt Sie zu einem Event DepositProceeded.
  • Letztendlich kann vom Konto auch wieder Guthaben auf ein anderes Konto überwiesen werden. Die relevanten Daten sind die selben wie die der Aktion ProcessDeposit und wir nennen die Aktion TransferMoney. Wenn die Aktion erfolgreich ausgeführt wurde, führt dies dazu, das ein Event mit dem Namen MoneyTransferred auftritt. 
  • Der Vollständigkeit halber gibt es auch noch eine Action CloseAccount und ein Event AccountClosed

 

Ein rein fiktives Szenario könnte dann so aussehen:

Exemplarischer Event-Store

Event Parameter Bedeutung
AccountRegistered Name: Max Mustermann
Kontonummer: 1
Es wird ein neues Konto mit der Kontonummer 1 auf den Namen Max Mustermann registriert.

Der Kontostand des Kontos ist jetzt 0,00 €.
DepositProceeded Kontonummer: 1
Betrag: 1500
Währung: EUR
Kurs: 1.00
Es werden 500 € auf das Konto mit der Kontonummer 1 eingezahlt.

Der Kontostand ist jetzt 1500,00 €.

Ein Wort zu Domain Driven Design

Domain Driven Design (DDD) ist ein Architektustil, der versucht, die Fachlichkeit bzw, die Fachdomäne in den Vordergrund zu stellen. Um es mit den Worten von Eric Evans, dem Vater von Domain Driven Design, zu sagen: Die Fachlichkeit der Anwendung ist das Herz Software. Für die Fachlogik der Anwendung ist es wichtig, dass alle beteiligten Personen eine einheitliche Sprache (ubiquotos lamguage) sprechen. Dabei ist es ein Ziel von Domain Driven Design, die Komplexität der Software zu reduzieren. 

Um dies zu erreichen unterscheidet man in Domain Driven Design zwischen Entitäten und Value Objects. In unserem Beispiel wäre eine Bankbewegung ein Value Object, denn zwei Bankbewegungen, die identisch sind, können nicht voneinander unterschieden werden. Sie können auch nicht alleine existieren, sondern ergeben erst im Zusammenhang mit einem Bankkonto Sinn. Auf der anderen Seite ist das Bankkonto eine Entität, denn ein Bankkonto wird durch eine Kontonummer eindeutig identifiziert. 

In Domain Driven Design versucht man nun, Aggregate zu finden. Aggregate sind Gruppen von Entiäten und Value Objects, die zusammen für die Domäne eine übergeordnete Bedeutung haben. In unserem Beispiel ist das Bankkonto ein solches Aggregat, welches aus dem Bankkonto selbst und allen Bewegungen besteht, die auf dem Konto ausgeführt wurden. Sicherlich ist die Bank verpflichtet, Informationen über den Kontoinhaber zu speichern. Dies könnte ein anderes Aggregat sein, zusammen mit der Adresse des Kontoinhabers und beispielsweise einer Kopie des Personalausweises. Das wichtige an einem Aggregat ist, dass es eine Entität gibt, die als "Aggregatwurzel" fungiert. Alle Operationen, egal ob sie die Aggregatwurzel betreffen, oder einen anderen Teil des Aggregats, dürfen nur durch die Aggregatwurzel angesprochen werden. 

Die Events, die wir definiert haben, sind für das Aggregat Bankkonto relevant und führen Zustandsveränderungen auf diesem Aggregat durch. Jedes Event führt zu einer neuen Version des Aggregats, bis der endgültige Zustand erreicht ist. 

Domain Events und Ubiquotos Language

Wie bereits erwähnt ist es ein Ziel von Domain Driven Design, eine einheitliche Sprache zu definieren, die sowohl Domänenanwender als auch IT-Spezialisten verstehen. Vielmehr ist es das Ziel, dass sich Fachleute und IT-Spezialisten in einer "allgegenwärtigen" Sprache unterhalten können. Eventsourcing verfolgt dieses Ziel, indem der Event-Store ohne Probleme von einem Domänenexperten gelesen werden kann. 

Scrollt nochmal hoch zu der Tabelle mit den Events aus unserem Beispiel-Szenario. Ihr werdet feststellen, dass keinerlei IT Kenntnisse notwendig sind, um zu erkennen, was in der Anwendung passiert ist. Grund hierfür ist die Benennung der Events auf eine weise, die der ubiquotos Language entspricht. Es werden keine technischen Begriffe wie "Record", "Tupel" oder ähnliches verwendet, sondern die Begriffe, auf die sich Domänenexperten und IT-Experten geeinigt haben. Darüber hinaus werden die Events immer in der Vergangenheit formuliert, also zum Beispiel AccountCreated anstatt CreateAccount. 

Die Verwendung der richtigen Namen ist extrem wichtig für die Software, weil hierdurch die Fachsprache Einzug erhält in die Software und durch die gemeinsame Sprache Domänenexperten sich viel besser verstanden fühlen. 

CQRS

Nun wissen wir, die die Domäne auf Ihre Events reagieren kann, aber woher kommen die Events? Wir haben anfangs von Aktionen gesprochen, die ausgeführt werden, um Events zu triggern. Wenn man Eventsourcing einsetzt, macht es oft Sinn, aber über CQRS nachzudenken. CQRS ist ein Architekturmuster und baut auf dem Muster CQS auf. 

CQS heisst "Command Query Separation" und bedeutet, dass eine Methode entweder eine Query-Methode sein kann, oder eine Command-Methode, aber nicht beides. Übersetzt bedeutet dass, dass eine Methode entweder Daten abfragt - dann hat sie eine Rückgabewert - oder ein Command ausführt und dann keine Daten zurückgibt. Eine Methode, die zum Beispiel einen neuen Benutzer anlegt und diesen dann nochmal liest und zurückgibt ist laut dem Pattern verboten. 

Command Query Responsive Separation (CQRS) baut auf diesem Pattern auf und dient zur Trennung zwischen Commands und Query-Operationen. Das Pattern geht noch ein wenig weiter, und erlaubt die Trennung von Read- und Write-Models. Dabei geht CQRS soweit, dass für verschiedene Situationen verschiedene Datenquellen aufgebaut werden. Der Aufbau der Datenquellen geschieht über sogenannte Denormalizer. Die Idee hinter einem Denormalizer ist es die normalisierte Datenspeicherung, die in relationalen Datenmodellen verwendet werden, für bestimmte Operationen aufzubrechen und Daten so zu speichern, dass effizienter damit gearbeitet werden kann.

Wenn wir zum Beispiel unsere Banking-Applikationen um Statistiken erweitern wollen, bietet es sich an, für die Statistiken ein eigenes Datenmodell zu wählen. Während das hinzufügen einer Tranaktion noch leicht machbar ist, wird das Auswerten aller Bankbewegungen sagen wir der letzten 10 Jahre aufgrund der vermutlich großen Datenmenge sehr rechenintensiv sein. Dazu kommt, dass heutzutage Speicher billiger ist als Rechenleistung, also Daten zu speichern ist effizienter als Daten zu berechnen. Aus diesem Grund wäre es für dieses Szenario zweckmäßig für die benötigte Statistik ein eigenes Query-Modell aufzubauen, welches auf eine eigene Datenbank oder andere Daten zugreift. 

Datenänderungen werden durch Commands durchgeführt, und diese Commands sind genau die, die wir anfangs Aktionen genannt haben. Damit wir nicht durcheinander kommen, werden wir diese Aktionen ab nun Commands nennen. Im Sinne der Ubiquotos Language definieren wir Commands im Imperativ:

CreateAccount -> AccountCreated

 

und wieder zurück zum Eventsourcing

Wenn wir nun mehrere Datenquellen haben, stellt sich die Frage, wie wir diese synchronisiert bekommen. Jede Änderung an unserer Datenbank zwingt uns, unsere zweite Datenquelle nachzuziehen. Auf der anderen Seite könnte es sein, dass Daten in der zweiten Datenquelle verändert werden, dann muss unsere Hauptdatenbank aktualisiert werden. Die Antwort auf diese Frage ist: der Event-Store. 

Da wir mit Eventsourcing arbeiten, brauchen wir uns keine Sorgen machen, dass unsere Datenbanken außeinander laufen. Wir brauchen einfach nur für jede Datenquelle einen Denormalizer, der auf alle Events reagiert und sobald diese empfangen werden, Daten in unserer Datenquelle und in der gewünschten Form anlegt. 

Das ganze geht soweit, dass wir unsere Datenbanken einfach löschen können und alle Events aus dem Eventstore durchlaufen. Dieser "Replay" Vorgang erzeugt dann unsere Datenbanken neu. Dieses Replay ist so genial, dass es möglich ist, jede Version des Aggregats zu jeder Zeit wiederherstellen zu können. Aus dem Event-Log könnte auch eine Versionierung erzeugt werden, da alle Änderungen im Event-Store vorliegen. 

Aber Achtung:

Das ganze funktioniert natürlich nur solange, solange sich der Denormalizer nicht ändert. Sobald auf eine andere Weise auf die Events reagiert wird, wird natürlich auch eine andere Datenbank erzeugt. Das ist meistens nicht gewünscht. Von daher sollte man sich im Vorfeld Gedanken über eine Versionierung Der Event-Subscriber machen. 

 

Eventsourcing und CQRS

Es kommt vielleicht nun der Verdacht auf, CQRS und EventSourcing gehören zusammen. Das zu denken wäre definitiv falsch! Ich möchte explizit darauf hinweisen, dass es sich bei Eventsourcing und bei CQRS um zwei Muster handelt, die vollkommen unabhängig voneinander sind. Zwar ist es so, dass die Muster sich optimal ergänzen, weil Eventsourcing eine ziemlich geniale Lösung für die Synchronisation mehrerer Datenquellen liefert und CQRS seinerseits die Lösung bietet, um Commands und Events von Query Operationen zu trennen. 

Es wäre aber durchaus möglich, für die Synchronisation der Datenquellen andere Technologien einzusetzen, zum Beispiel eine Datenbankreplikation. Und auch die Trennung von Commands und Abfragen ist auch möglich, zum Beispiel über entsprechende Services. 

Zurück

Einen Kommentar schreiben

Nach oben