2016-07-25 33 views
0

In Event-Sourcing-, speichern Sie alle Einzel Domain Event, die für eine Aggregate Instanz geschehen, bekannt als Event-Stream-. Zusammen mit Event Stream speichern Sie auch eine Stream Version.Stream-Version in dem Ereignisse Sourcing-

Sollte die Version mit jedem Domain Ereignis, oder sollte es mit Transaktionsänderungen (auch bekannt als Befehle) in Beziehung gesetzt werden zusammenhängen?


Beispiel:

Unser aktueller Stand des Event-Store ist:

aggregate_id | version | event 
-------------|---------|------ 
1   | 1  | E1 
1   | 2  | E2 

Ein neuer Befehl in Aggregate 1 ausgeführt wird Dieser Befehl erzeugt zwei neue Ereignisse E3 und E4.

Ansatz 1:

aggregate_id | version | event 
-------------|---------|------ 
1   | 1  | E1 
1   | 2  | E2 
1   | 3  | E3 
1   | 4  | E4 

Mit diesem Ansatz Parallelität kann 3 durch Speichermechanismus mit eindeutigen Index, aber die Wiedergabe der Ereignisse bis zur Version getan werden, um das Aggregat/System in einem inkonsistenten Zustand.

Ansatz 2:

aggregate_id | version | event 
-------------|---------|----- 
1   | 1  | E1 
1   | 2  | E2 
1   | 3  | E3 
1   | 3  | E4 

Wiederholen der Ereignisse bis zur Version 3 verlassen das Aggregat/System in einem konsistenten Zustand.

Danke!

+0

Ich forsche immer noch Pro/Contra von zwei Ansätze. Außerdem wird geprüft, welcher Ansatz in IDDD- und anderen DDD-Büchern verwendet wird. – martinezdelariva

Antwort

1

Kurze Antwort: # 1.

Das Schreiben der Ereignisse E3 und E4 sollte Teil derselben Transaktion sein.

Beachten Sie, dass sich die beiden Ansätze nicht wirklich unterscheiden, wenn Sie sich Sorgen machen. Wenn Ihr Leser im ersten Fall E4 nicht lesen kann, dann können Sie auch im zweiten Fall lesen. In dem Anwendungsfall, in dem Sie das Aggregat laden, um einen Schreibvorgang auszuführen; Das Laden der ersten drei Ereignisse wird Ihnen sagen, dass die nächste Version # 4 sein sollte.

Im Fall von Ansatz # 1 erzeugt der Versuch, Version 4 zu schreiben, einen eindeutigen Integritätskonflikt; Der Command-Handler kann nicht erkennen, ob das Problem eine schlechte Auslastung der Daten oder einfach ein optimistischer Concurrency-Fehler war, aber in beiden Fällen ist das Ergebnis kein Schreiben, und das Buch des Datensatzes befindet sich immer noch in einem konsistenten Zustand.

Im Fall von Ansatz 2 steht der Versuch, Version 4 zu schreiben, nicht in Konflikt mit irgendetwas. Der Schreibvorgang ist erfolgreich, und jetzt haben Sie E5, das nicht mit E4 konsistent ist. Bleah.

Für Referenzen auf Schemas für Ereignisspeicher, könnten Sie reviewing betrachten:

Mein bevorzugtes Schema, vorausgesetzt, dass Sie gezwungen sind, Ihre zu rollen besitzen, trennt den Strom von den Ereignissen.

stream_id | sequence | event_id 
-------------|----------|------ 
1   | 1  | E1 
1   | 2  | E2 

Der Strom gibt Ihnen einen Filter (Strom-ID), die Ereignisse zu identifizieren, die Sie möchten, und eine Reihenfolge (Sequenz) die Ereignisse, um sicherzustellen, Sie in der gleichen Reihenfolge wie die Ereignisse zu lesen sind Sie schreiben. Aber darüber hinaus ist es eine Art künstliches Ding, ein Nebeneffekt der Art und Weise, wie wir zufällig unsere Gesamtgrenzen gewählt haben. Daher sollte seine Rolle ziemlich begrenzt sein.

Die tatsächlichen Ereignisdaten, die woanders leben.

event_id | data | meta_data | ... 
-------------------------------------- 
E1  | ... | ... | ... 
E2  | ... | ... | ... 

Wenn Sie die Ereignisse mit einem bestimmten Befehl zugeordnet identifizieren zu können, das ist Teil der Veranstaltung Meta-Daten, die nicht Teil des Stroms Geschichte (siehe: correlationId, causationId).

0

Ansatz 1 ist das, was ich verwendet habe und gesehen andere verwenden - nur eine fortlaufende Nummer für die Veranstaltung, die oft als Eventnumber

Die Parallelität Teil ist einfach so, dass, wenn Sie Ihr Aggregat laden, wissen Sie, was die neueste Veranstaltung ist. Sie bearbeiten dann den Befehl und speichern alle resultierenden Ereignisse. Wenn Sie etwas über der von Ihnen geladenen Nummer sehen, bedeutet dies, dass Sie bereits veraltet sind und entsprechend handeln können. Andernfalls können Sie die Ereignisse speichern.

+0

Ich sehe dieses Konzept nicht wirklich als 'Versionierung' in Bezug auf Befehle - möchten Sie in der Lage sein, den Status des Aggregats pro verarbeitetem Befehl zu kennen? I.e Befehl 1 erzeugt 1 Ereignis, Befehl 2 erzeugt 3 Ereignisse, aber die 'Version' ist 2 statt 4? – tomliversidge

2

Nichts hindert Sie daran, eine commit_sequence zusammen mit einer version einzuführen.

Zum Beispiel in NEventStore können Sie sehen, dass ein Festschreiben eine StreamRevision (Version - Erhöhung für jedes Ereignis) und eine CommitSequence hat.

0

In Domain Driven Design mit Event-Sourcing stellt ein Aggregat eine Konsistenzgrenze und seine Invarianten muss am Anfang und Ende jeden Befehl (oder Funktionsaufruf) wahr sein. Sie können eine Invariante in der Mitte einer Elementfunktion verletzen, solange sie am Ende nicht verletzt wird.

Was Sie in Ihrem Beitrag darauf hingewiesen haben, ist das sehr aufschlussreich. Das heißt, wenn ein einzelner Befehl (oder Aufruf einer Member-Funktion) für ein Aggregat mehrere Ereignisse erzeugt, dann kann das Speichern nur einiger dieser Ereignisse zu einer Verletzung Ihrer Invariante führen, wenn ein anderer Prozess das Aggregat von der Platte neu lädt. Wenn eine SQL-Datenbank als Ereignisspeicher verwendet wird, gibt es eine Reihe von verwandten Szenarien, die dieses Problem verursachen können.

Die erste (und einfachste) Möglichkeit, dies zu vermeiden, besteht darin, alle event-INSERT-Anweisungen in eine Transaktion zu schreiben, so dass entweder alle Ereignisse beibehalten werden oder keine (z. B. aufgrund von Parallelität). Auf diese Weise wird Ihre "on disk" -Darstellung der Invariante beibehalten. Sie müssen außerdem sicherstellen, dass die Transaktionsisolationsstufe nicht "READ UNCOMMITTED" lautet (damit andere Prozesse nicht die Hälfte Ihres Commits sehen). Sie müssen auch sicherstellen, dass die Datenbank Ereignissequenznummern zwischen Prozessen nicht verschachtelt. Z. B. weist die Datenbank die Sequenznummer "1" für ein Ereignis in dem Prozess A, die Sequenznummer "2" für ein Ereignis in dem Prozess B, dann die Sequenznummer "3" für ein zweites Ereignis in dem Prozess A erneut zu.Alle Ereignisse können an die Datenbank übergeben werden, da keine Konflikte in der Parallelitätseinschränkung (der Aggregat-ID und der Ereignisfolgenummer) bestehen, die Ereignisfolge jedoch von zwei Prozessen geschrieben wurde und Ihre Invariante daher möglicherweise weiterhin verletzt wird.

Eine zweite Option besteht darin, alle Ihre Ereignisse in ein Array zu schreiben, das mit einer einzigen INSERT-Anweisung beibehalten wird. Dies führt im Wesentlichen dazu, dass Sie pro Commit eine Versionsnummer anstelle einer Versionsnummer pro Event haben. Für mich ist dies logischer, aber es erfordert, dass Sie eine Prozedur haben, um das Event-Array zu "entfalten", bevor Sie es an die verschiedenen Event-Handler und Prozess-Manager senden. Ich verwende diesen zweiten Mechanismus persönlich in einem Projekt, das Ereignisse im rohen Binärformat auf der Festplatte speichert. Die Ereignisse selbst enthalten nur die minimale Menge an Information, die notwendig ist, damit das Aggregat den Zustand ändert - die Ereignisse beinhalten kein Aggregat. Die commit enthält andererseits die Aggregat-ID, die Commit-Sequenznummer und verschiedene andere Metadaten. Das trennt im Wesentlichen Funktionalität zwischen dem -Aggregat als einen Handler für nicht übermittelte Ereignisse und Ereignishandler für festgeschriebene Ereignisse. Diese Unterscheidung macht auch Sinn, denn wenn ein Ereignis eine "Tatsache" ist - etwas ist passiert - dann gibt es einen Unterschied zwischen dem, was das Aggregat getan hat und ob das, was das Aggregat tat, tatsächlich auf der Platte gespeichert wurde.

In einer theoretischen Anmerkung, ein gutes Beispiel für Ihr Problem ist das der verketteten Listen - denken Sie nur an eine In-Memory-Darstellung: keine Persistenz auf der Festplatte. Einer der Gründe, warum Sie eine verkettete Liste über einen Vektor oder ein Array verwenden, ist, dass es effiziente Einfügungen von Knoten ermöglicht (gut, effizienter als Arrays). Die Einfügeoperation erfordert typischerweise, dass der "nächste" Zeiger des aktuellen Knotens auf die Speicheradresse des neuen Knotens gesetzt wird und der "nächste" Zeiger des neuen Knotens auf den vorherigen "nächsten" Zeiger des aktuellen Knotens gesetzt wird. Wenn ein anderer Prozess die gleiche verknüpfte Liste im Speicher nach der ersten abgeschlossenen Operation, aber vor der zweiten Operation gelesen hat, werden nicht alle Knoten in der verknüpften Liste angezeigt. Wenn jede "Operation" wie ein "Ereignis" ist, dann bewirkt nur das Sehen des ersten Ereignisses, dass der Leser eine unterbrochene verknüpfte Liste sieht.