Ich habe also schon eine Weile mit Event Sourcing und CQRS geflirtet, obwohl ich nie die Gelegenheit hatte, die Muster auf ein reales Projekt anzuwenden.
Ich verstehe die Vorteile der Trennung von Lese- und Schreibproblemen und weiß zu schätzen, wie mit Event Sourcing Statusänderungen in Datenbanken mit Lesemodell, die sich von Ihrem Event Store unterscheiden, auf einfache Weise projiziert werden können.
Was mir nicht klar ist, warum Sie Ihre Aggregate jemals aus dem Event Store selbst rehydrieren würden.
Wenn das Projizieren von Änderungen an "Lese" -Datenbanken so einfach ist, warum nicht immer Änderungen an einer "Schreib" -Datenbank projizieren, deren Schema perfekt zu Ihrem Domänenmodell passt? Dies wäre effektiv eine Snapshot-Datenbank.
Ich stelle mir vor, dass dies in ES + CQRS-Anwendungen in der Natur ziemlich häufig vorkommt.
Wenn dies der Fall ist, ist der Ereignisspeicher nur dann nützlich, wenn Sie Ihre "Write" -Datenbank aufgrund von Schemaänderungen neu erstellen? Oder vermisse ich etwas Größeres?
quelle
Antworten:
Weil die "Ereignisse" das Buch der Aufzeichnungen sind.
Ja; Manchmal ist es eine nützliche Leistungsoptimierung, eine zwischengespeicherte Kopie des Aggregatzustands zu verwenden, anstatt diesen Zustand jedes Mal von Grund auf neu zu generieren. Denken Sie daran: Die erste Regel zur Leistungsoptimierung lautet "Don't". Die Lösung wird dadurch komplexer, und Sie möchten dies lieber vermeiden, bis Sie eine überzeugende Geschäftsmotivation haben.
Ihnen fehlt etwas Größeres.
Der erste Punkt ist, dass Sie, wenn Sie sich für eine ereignisbasierte Lösung entscheiden, erwarten, dass die Historie des Geschehens erhalten bleibt, dh, Sie möchten zerstörungsfreie Änderungen vornehmen .
Deshalb schreiben wir überhaupt an den Event Store.
Dies bedeutet insbesondere, dass jede Änderung in den Ereignisspeicher geschrieben werden muss.
Konkurrierende Autoren können möglicherweise die Schreibvorgänge des jeweils anderen zerstören oder das System in einen unbeabsichtigten Zustand versetzen, wenn sie sich der Änderungen des anderen nicht bewusst sind. Der übliche Ansatz, wenn Sie Konsistenz benötigen, besteht darin, Ihre Schreibvorgänge an eine bestimmte Position im Journal zu richten (analog zu einem bedingten PUT in einer HTTP-API). Ein fehlgeschlagener Schreibvorgang weist den Schreiber darauf hin, dass sein derzeitiges Verständnis des Journals nicht synchron ist und dass er sich erholen sollte.
Die Rückkehr zu einer bekannten guten Position und die Wiederholung weiterer Ereignisse seit diesem Zeitpunkt ist eine gängige Wiederherstellungsstrategie. Diese bekannte gute Position kann eine Kopie des lokalen Caches oder eine Darstellung in Ihrem Snapshot-Speicher sein.
Auf dem glücklichen Pfad können Sie eine Momentaufnahme des Aggregats im Speicher behalten. Sie müssen sich nur an ein externes Geschäft wenden, wenn keine lokale Kopie verfügbar ist.
Darüber hinaus müssen Sie nicht vollständig eingeholt werden, wenn Sie Zugriff auf das Geschäftsbuch haben.
Daher besteht der übliche Ansatz ( bei Verwendung eines Snapshot-Repositorys) darin, es asynchron zu verwalten . Auf diese Weise können Sie eine Wiederherstellung durchführen, ohne den gesamten Verlauf des Aggregats erneut zu laden und abzuspielen.
In vielen Fällen ist diese Komplexität nicht von Interesse, da feinkörnige Aggregate mit begrenzter Lebensdauer normalerweise nicht genügend Ereignisse erfassen, um die Kosten für die Verwaltung eines Snapshot-Caches zu übersteigen.
Wenn es sich jedoch um das richtige Tool für das Problem handelt, ist es durchaus sinnvoll, eine veraltete Darstellung des Aggregats in das Schreibmodell zu laden und es anschließend mit zusätzlichen Ereignissen zu aktualisieren.
quelle
Da Sie nicht angeben, was der Zweck der "Schreib" -Datenbank sein soll, gehe ich hier davon aus, dass Sie Folgendes meinen: Wenn Sie ein neues Update für ein Aggregat registrieren, anstatt das Aggregat aus dem Ereignisspeicher neu zu erstellen Heben Sie es aus der "Write" -Datenbank, überprüfen Sie die Änderung und geben Sie ein Ereignis aus.
Wenn dies so gemeint ist, schafft diese Strategie eine Bedingung für Inkonsistenz: Wenn ein neues Update durchgeführt wird, bevor das letzte Update die Möglichkeit hatte, in die Datenbank "write" zu gelangen, wird das neue Update gegen veraltete Daten validiert. Auf diese Weise wird möglicherweise ein "unmögliches" (dh "unzulässiges") Ereignis ausgegeben und der Systemstatus beschädigt.
Betrachten Sie beispielsweise ein Beispiel für die Buchung von Sitzplätzen in einem Theater. Um Doppelbuchungen zu vermeiden, müssen Sie sicherstellen, dass der gebuchte Sitzplatz nicht bereits belegt ist - dies wird als "Bestätigung" bezeichnet. Dazu hinterlegen Sie eine Liste der bereits gebuchten Plätze in der Datenbank "write". Wenn dann eine Buchungsanfrage eingeht, prüfen Sie, ob der angeforderte Platz in der Liste enthalten ist. Wenn nicht, geben Sie eine "gebuchte" Veranstaltung aus, andernfalls antworten Sie mit einer Fehlermeldung. Anschließend führen Sie einen Projektionsprozess durch, in dem Sie die "gebuchten" Ereignisse abhören und die gebuchten Plätze zur Liste in der Datenbank "Schreiben" hinzufügen.
Normalerweise würde das System so funktionieren:
Was ist jedoch, wenn Anforderungen zu schnell eingehen und Schritt 5 vor Schritt 4 erfolgt?
Jetzt haben Sie zwei Termine, um denselben Platz zu buchen. Der Systemstatus ist beschädigt.
Um dies zu verhindern, sollten Sie Aktualisierungen niemals anhand einer Projektion validieren. Um ein Update zu validieren, erstellen Sie das Aggregat aus dem Ereignisspeicher neu und validieren das Update anhand dessen. Danach geben Sie ein Ereignis aus, verwenden jedoch den Zeitstempelschutz, um sicherzustellen, dass seit dem letzten Lesen aus dem Speicher keine neuen Ereignisse ausgegeben wurden. Wenn dies fehlschlägt, versuchen Sie es erneut.
Das Neuerstellen von Aggregaten aus dem Ereignisspeicher kann zu einer Leistungsbeeinträchtigung führen. Um dies zu vermeiden, können Sie aggregierte Snapshots direkt im Ereignisstrom speichern, die mit der ID des Ereignisses versehen sind, aus dem der Snapshot erstellt wurde. Auf diese Weise können Sie das Aggregat neu erstellen, indem Sie den letzten Snapshot laden und nur Ereignisse wiedergeben, die danach aufgetreten sind, anstatt immer den gesamten Ereignisstrom vom Anfang der Zeit an wiederzugeben.
quelle
Der Hauptgrund ist die Leistung. Sie können für jedes Commit einen Snapshot speichern (Commit = die Ereignisse, die von einem einzelnen Befehl generiert werden, normalerweise nur ein Ereignis), dies ist jedoch kostspielig. Entlang des Schnappschusses müssen Sie auch das Commit speichern, sonst wäre es kein Event-Sourcing. Und das alles muss atomar gemacht werden, alles oder nichts. Ihre Frage ist nur gültig, wenn separate Datenbanken / Tabellen / Sammlungen verwendet werden (andernfalls wäre dies genau das Ereignis-Sourcing), sodass Sie gezwungen sind, Transaktionen zu verwenden , um die Konsistenz zu gewährleisten. Transaktionen sind nicht skalierbar. Ein Nur-Anhängen-Ereignisstrom (der Ereignisspeicher) ist die Mutter der Skalierbarkeit.
Der zweite Grund ist die Gesamtkapselung. Sie müssen es schützen. Dies bedeutet, dass das Aggregat jederzeit die Möglichkeit haben sollte, seine interne Darstellung zu ändern. Wenn Sie es speichern und stark davon abhängig sind, werden Sie Schwierigkeiten mit der Versionierung haben, was mit Sicherheit passieren wird. In der Situation , wenn Sie den Snapshot als Optimierungs nur verwenden, wenn Schemaänderungen Sie einfach diese Schnappschüsse ignorieren ( einfach ich wirklich nicht so denken, viel Glück bestimmt wird, dass Aggregate von Schemaänderungen - einschließlich aller verschachtelten Einheiten und Wertobjekte - in einem? effiziente Art und Weise und das Management).
quelle