Wie könnte das Schema aussehen, wenn ich ein RDBMS (z. B. SQL Server) zum Speichern von Ereignisbeschaffungsdaten verwenden würde?
Ich habe einige Variationen gesehen, über die abstrakt gesprochen wurde, aber nichts Konkretes.
Angenommen, man hat eine "Produkt" -Entität, und Änderungen an diesem Produkt können in Form von: Preis, Kosten und Beschreibung erfolgen. Ich bin verwirrt darüber, ob ich:
- Haben Sie eine "ProductEvent" -Tabelle, die alle Felder für ein Produkt enthält, wobei jede Änderung einen neuen Datensatz in dieser Tabelle bedeutet, sowie "wer, was, wo, warum, wann und wie" (WWWWWH). Wenn Kosten, Preis oder Beschreibung geändert werden, wird eine ganz neue Zeile hinzugefügt, um das Produkt darzustellen.
- Speichern Sie Produktkosten, Preis und Beschreibung in separaten Tabellen, die mit einer Fremdschlüsselbeziehung mit der Produkttabelle verknüpft sind. Wenn Änderungen an diesen Eigenschaften auftreten, schreiben Sie gegebenenfalls neue Zeilen mit WWWWWH.
- Speichern Sie WWWWWH sowie ein serialisiertes Objekt, das das Ereignis darstellt, in einer "ProductEvent" -Tabelle. Dies bedeutet, dass das Ereignis selbst in meinem Anwendungscode geladen, de-serialisiert und erneut abgespielt werden muss, um den Anwendungsstatus für ein bestimmtes Produkt neu zu erstellen .
Besonders mache ich mir Sorgen um Option 2 oben. Im Extremfall wäre die Produkttabelle fast eine Tabelle pro Eigenschaft. Wenn der Anwendungsstatus für ein bestimmtes Produkt geladen werden soll, müssen alle Ereignisse für dieses Produkt aus jeder Produktereignistabelle geladen werden. Diese Tischexplosion riecht für mich falsch.
Ich bin sicher, "es kommt darauf an", und obwohl es keine einzige "richtige Antwort" gibt, versuche ich, ein Gefühl dafür zu bekommen, was akzeptabel und was überhaupt nicht akzeptabel ist. Mir ist auch bewusst, dass NoSQL hier helfen kann, wo Ereignisse gegen einen aggregierten Stamm gespeichert werden können, dh nur eine einzige Anforderung an die Datenbank, um die Ereignisse zum erneuten Erstellen des Objekts abzurufen, aber wir verwenden keine NoSQL-Datenbank an der Moment, also suche ich nach Alternativen.
quelle
Antworten:
Der Ereignisspeicher sollte nicht über die spezifischen Felder oder Eigenschaften von Ereignissen informiert sein müssen. Andernfalls würde jede Änderung Ihres Modells dazu führen, dass Ihre Datenbank migriert werden muss (genau wie bei einer guten, altmodischen, zustandsbasierten Persistenz). Daher würde ich Option 1 und 2 überhaupt nicht empfehlen.
Unten sehen Sie das in Ncqrs verwendete Schema . Wie Sie sehen können, speichert die Tabelle "Ereignisse" die zugehörigen Daten als CLOB (dh JSON oder XML). Dies entspricht Ihrer Option 3 (Nur dass es keine "ProductEvents" -Tabelle gibt, da Sie nur eine generische "Events" -Tabelle benötigen. In Ncqrs erfolgt die Zuordnung zu Ihren Aggregate Roots über die "EventSources" -Tabelle, wobei jede EventSource einer tatsächlichen Tabelle entspricht Gesamtwurzel.)
Der SQL-Persistenzmechanismus der Event Store-Implementierung von Jonathan Oliver besteht im Wesentlichen aus einer Tabelle mit dem Namen "Commits" und einem BLOB-Feld "Payload". Dies ist so ziemlich das Gleiche wie in Ncqrs, nur dass die Eigenschaften des Ereignisses im Binärformat serialisiert werden (wodurch beispielsweise Verschlüsselungsunterstützung hinzugefügt wird).
Greg Young empfiehlt einen ähnlichen Ansatz, der auf Gregs Website ausführlich dokumentiert ist .
Das Schema seiner prototypischen "Ereignisse" -Tabelle lautet:
quelle
Das GitHub-Projekt CQRS.NET enthält einige konkrete Beispiele dafür, wie Sie EventStores in verschiedenen Technologien ausführen können. Zum Zeitpunkt des Schreibens gibt es eine Implementierung in SQL mit Linq2SQL und ein dazugehöriges SQL-Schema , eine für MongoDB , eine für DocumentDB (CosmosDB, wenn Sie in Azure sind) und eine für EventStore (wie oben erwähnt). In Azure gibt es mehr wie Tabellenspeicher und Blob-Speicher, der dem Flatfile-Speicher sehr ähnlich ist.
Ich denke, der Hauptpunkt hier ist, dass sie alle dem gleichen Auftraggeber / Vertrag entsprechen. Sie alle speichern Informationen an einem einzigen Ort / Container / in einer einzigen Tabelle. Sie verwenden Metadaten, um ein Ereignis von einem anderen zu identifizieren, und speichern das gesamte Ereignis so, wie es war - in einigen Fällen serialisiert, in unterstützenden Technologien, wie es war. Je nachdem, ob Sie eine Dokumentendatenbank, eine relationale Datenbank oder sogar eine Flatfile auswählen, gibt es verschiedene Möglichkeiten, um alle die gleiche Absicht eines Ereignisspeichers zu erreichen (es ist nützlich, wenn Sie Ihre Meinung zu irgendeinem Zeitpunkt ändern und feststellen, dass Sie migrieren oder unterstützen müssen mehr als eine Speichertechnologie).
Als Entwickler des Projekts kann ich einige Einblicke in einige der von uns getroffenen Entscheidungen geben.
Erstens haben wir festgestellt (auch bei eindeutigen UUIDs / GUIDs anstelle von Ganzzahlen), dass aus strategischen Gründen sequenzielle IDs aus strategischen Gründen auftreten. Daher war es für einen Schlüssel nicht eindeutig genug, nur eine ID zu haben. Daher haben wir unsere Haupt-ID-Schlüsselspalte mit den Daten / zusammengeführt. Objekttyp, um einen wirklich (im Sinne Ihrer Anwendung) eindeutigen Schlüssel zu erstellen. Ich weiß, dass einige Leute sagen, dass Sie es nicht speichern müssen, aber das hängt davon ab, ob Sie auf der grünen Wiese sind oder mit vorhandenen Systemen koexistieren müssen.
Wir haben uns aus Gründen der Wartbarkeit an einen einzelnen Container / eine Tabelle / eine Sammlung gehalten, aber wir haben mit einer separaten Tabelle pro Entität / Objekt herumgespielt. Wir haben in der Praxis festgestellt, dass entweder die Anwendung "CREATE" -Berechtigungen benötigt (was im Allgemeinen keine gute Idee ist ... im Allgemeinen gibt es immer Ausnahmen / Ausschlüsse) oder jedes Mal, wenn eine neue Entität / ein neues Objekt existiert oder bereitgestellt wurde, neu Lagerbehälter / Tabellen / Sammlungen mussten gemacht werden. Wir fanden, dass dies für die lokale Entwicklung schmerzlich langsam und für Produktionsbereitstellungen problematisch war. Sie mögen nicht, aber das war unsere reale Erfahrung.
Eine andere Sache, an die Sie sich erinnern sollten, ist, dass das Auffordern von Aktion X dazu führen kann, dass viele verschiedene Ereignisse auftreten, sodass Sie alle Ereignisse kennen, die von einem Befehl / Ereignis / was auch immer nützlich ist, generiert werden. Sie können sich auch auf verschiedene Objekttypen erstrecken, z. B. kann das Drücken von "Kaufen" in einem Einkaufswagen dazu führen, dass Konto- und Lagerereignisse ausgelöst werden. Eine konsumierende Anwendung möchte dies alles wissen, daher haben wir eine Korrelations-ID hinzugefügt. Dies bedeutete, dass ein Verbraucher nach allen Ereignissen fragen konnte, die aufgrund seiner Anfrage ausgelöst wurden. Das sehen Sie im Schema .
Insbesondere bei SQL haben wir festgestellt, dass die Leistung zu einem Engpass wurde, wenn Indizes und Partitionen nicht ausreichend verwendet wurden. Denken Sie daran, dass Ereignisse in umgekehrter Reihenfolge gestreamt werden müssen, wenn Sie Snapshots verwenden. Wir haben einige verschiedene Indizes ausprobiert und festgestellt, dass in der Praxis einige zusätzliche Indizes zum Debuggen von realen Anwendungen in der Produktion erforderlich sind. Das sehen Sie wieder im Schema .
Andere produktionsinterne Metadaten waren bei produktionsbasierten Untersuchungen hilfreich. Zeitstempel gaben uns einen Einblick in die Reihenfolge, in der Ereignisse fortbestanden oder ausgelöst wurden. Dies gab uns Unterstützung bei einem besonders stark ereignisgesteuerten System, das eine große Anzahl von Ereignissen auslöste und uns Informationen über die Leistung von Dingen wie Netzwerken und die Systemverteilung über das Netzwerk gab.
quelle
Vielleicht möchten Sie sich Datomic ansehen.
Datomic ist eine Datenbank mit flexiblen, zeitbasierten Fakten , die Abfragen und Verknüpfungen mit elastischer Skalierbarkeit und ACID-Transaktionen unterstützen.
Ich schrieb eine detaillierte Antwort hier
Sie können einen Vortrag von Stuart Halloway Erklären der Gestaltung von Datomic sehen hier
Da Datomic Fakten rechtzeitig speichert, können Sie sie für Anwendungsfälle zur Ereignisbeschaffung und vieles mehr verwenden.
quelle
Ein möglicher Hinweis ist, dass das Design gefolgt von "Langsam wechselnde Dimension" (Typ = 2) Ihnen dabei helfen sollte, Folgendes abzudecken:
Die Linksfalzfunktion sollte ebenfalls in Ordnung zu implementieren sein, aber Sie müssen über die zukünftige Komplexität der Abfrage nachdenken.
quelle
Ich denke, Lösung (1 & 2) kann sehr schnell zu einem Problem werden, wenn sich Ihr Domain-Modell weiterentwickelt. Neue Felder werden erstellt, einige ändern ihre Bedeutung und andere können nicht mehr verwendet werden. Schließlich wird Ihre Tabelle Dutzende von nullbaren Feldern haben, und das Laden der Ereignisse wird chaotisch sein.
Denken Sie auch daran, dass der Ereignisspeicher nur für Schreibvorgänge verwendet werden sollte. Sie fragen ihn nur ab, um die Ereignisse zu laden, nicht die Eigenschaften des Aggregats. Sie sind getrennte Dinge (das ist die Essenz von CQRS).
Lösung 3 Was Menschen normalerweise tun, gibt es viele Möglichkeiten, dies zu erreichen.
Beispielsweise erstellt EventFlow CQRS bei Verwendung mit SQL Server eine Tabelle mit diesem Schema:
wo:
Wenn Sie jedoch von Grund auf neu erstellen, würde ich empfehlen, dem YAGNI-Prinzip zu folgen und mit den minimal erforderlichen Feldern für Ihren Anwendungsfall zu erstellen.
quelle
Ich denke, dies wäre eine späte Antwort, aber ich möchte darauf hinweisen, dass die Verwendung von RDBMS als Event-Sourcing-Speicher durchaus möglich ist, wenn Ihre Durchsatzanforderungen nicht hoch sind. Ich möchte Ihnen nur Beispiele für ein Event-Sourcing-Ledger zeigen, das ich zur Veranschaulichung erstellt habe.
https://github.com/andrewkkchan/client-ledger-service Bei dem oben genannten handelt es sich um einen Event-Sourcing-Ledger-Webdienst. https://github.com/andrewkkchan/client-ledger-core-db Und das oben Genannte Ich verwende RDBMS, um Zustände zu berechnen, damit Sie alle Vorteile einer RDBMS-ähnlichen Transaktionsunterstützung nutzen können. https://github.com/andrewkkchan/client-ledger-core-memory Und ich habe einen anderen Verbraucher, der im Speicher verarbeitet werden muss, um Bursts zu verarbeiten.
Man würde argumentieren, dass der oben genannte tatsächliche Ereignisspeicher immer noch in Kafka lebt - da RDBMS nur langsam eingefügt werden kann, insbesondere wenn das Einfügen immer angehängt wird.
Ich hoffe, der Code hilft Ihnen, eine Illustration zu geben, abgesehen von den sehr guten theoretischen Antworten, die bereits für diese Frage gegeben wurden.
quelle