Event-Sourcing, Wiedergabe und Versionierung

8

Ich entwerfe ein System, das Event Sourcing, CQRS und Microservices verwendet. Ich muss verstehen, dass dies kein ungewöhnliches Muster ist. Ein Schlüsselmerkmal des Dienstes muss die Fähigkeit sein, aus einem Aufzeichnungssystem zu rehydrieren / wiederherzustellen. Microservices erzeugen Befehle und Abfragen auf einem MQ (Kafka). Andere Microservices reagieren (Ereignisse). Befehle und Abfragen werden in S3 zum Zweck der Überwachung und Wiederherstellung beibehalten.

Der aktuelle Denkprozess war, dass wir zum Wiederherstellen des Systems das Ereignisprotokoll aus S3 extrahieren und es einfach an Kafka zurückmelden konnten.

Dies berücksichtigt jedoch nicht die Veränderungen sowohl bei den Herstellern als auch bei den Verbrauchern im Laufe der Zeit. Die Versionierung auf Befehls- / Abfrageebene scheint einen Beitrag zur Lösung des Problems zu leisten, aber ich kann mich nicht mit der Versionierung von Verbrauchern befassen, sodass ich erzwingen kann, dass ein Befehl, der während einer Wiederherstellung empfangen und verarbeitet wird, genau derselbe ist [Version des] Codes, der die Verarbeitung ausführt, da der Befehl zum ersten Mal empfangen wurde.

Gibt es Muster, mit denen ich das lösen kann? Kennt jemand andere Systeme, die für diese Funktion werben?

BEARBEITEN: Ein Beispiel hinzufügen.

Ein "Käufer" sendet eine "Frage" an einen "Verkäufer" auf meiner Auktionsseite. Der Fluss sieht wie folgt aus: UI -> Web App: POST /question {:text text :to seller-id :from user-id} Web App -> MQ: SEND {:command send-question :args [text seller-id user-id]} MQ -< Audit: <command + args appended to log in S3> MQ -< Questions service: - Record question in DB - Email seller 'You have a question'

Aufgrund einer neuen Geschäftsanforderung passe ich jetzt den Verbraucher "Fragendienst" an, um die Anzahl aller ungelesenen Fragen beizubehalten. Das DB-Schema wird geändert. Bisher hatten wir keine Ahnung, ob der Verkäufer eine Frage gelesen hat oder nicht. Die letzte Zeile wird:

MQ -< Questions service: - Record question in DB - Email seller 'You have a question' - Increment 'unread questions count'

Zwei Befehle sind Probleme, einer vor der Änderung und einer nach der Änderung. Die Anzahl der ungelesenen Fragen entspricht 1.

Das System stürzt ab. Wir haben wiederhergestellt, indem wir die Befehle über den neuen Code wiedergegeben haben. Am Ende der Wiederherstellung beträgt die Anzahl unserer "ungelesenen Fragen" 2. Auch wenn das Ergebnis in diesem erfundenen Beispiel keine Katastrophe ist, ist der wiederhergestellte Zustand nicht der vorherige.

Antony Woods
quelle
Die Frage scheint ein paar Bedenken zu vermischen. Event Sourcing ist eine Architekturstrategie für Systeme, bei denen viele Änderungen an den zugrunde liegenden Daten vorgenommen werden. Die Versionierung von DTOs, Backups und Daten ist eine ganz andere Sache. Event Sourcing wurde nicht speziell entwickelt, um Ihnen Bare-Metal-Wiederherstellungsfunktionen zu bieten. Dazu benötigen Sie eine bestimmte Strategie.
theMayer
Vielleicht kann Event-Upcasting Ihr Problem lösen: blog.trifork.com/2012/04/17/…
Songo
@ rmayer06 Ich denke, ein Beispiel ist das, wonach ich suche!
Antony Woods

Antworten:

16

Erstens ist es wichtig, den Unterschied zwischen Befehlen und Ereignissen zu verstehen und nutzen zu können .

Wie diese Frage kurz und bündig zeigt, sind Befehle Dinge, die wir gerne tun würden, und Ereignisse sind Dinge, die bereits geschehen sind. Ein Befehl führt nicht unbedingt zu einem signifikanten Ereignis im System, aber normalerweise. Beispielsweise kann ein send messageBefehl abgelehnt werden. In diesem Fall tritt kein Ereignis auf (normalerweise wird ein Fehler nicht als Ereignis in diesem Sinne betrachtet, obwohl wir ihn möglicherweise weiterhin in einem Diagnoseprotokoll protokollieren). Wenn der send messageBefehl akzeptiert wird, tritt das message sentEreignis auf und Ereignisdetails können den Absender, den Empfänger und den Inhalt beschreiben.

Wenn wir über den Systemzustand sprechen, diskutieren wir tatsächlich nicht einen Höhepunkt von Befehlen, sondern von Ereignissen. Nur Ereignisse können eine Statusänderung im System verursachen. Angenommen, ich gehe zum örtlichen Publix-Supermarkt und kaufe einen Lottoschein für Florida. Der Befehl lautete "Ticket kaufen" und die Veranstaltung lautete "Ticket ausgestellt". Mein nächster Befehl ist dann zur Lotterie, meine Zahlen für den PowerBall zu ziehen. Die Lotterie wird meinen Befehl ignorieren (aber ich habe keine Kenntnis), und das Ereignis "PowerBall-Nummern ausgewählt" findet unabhängig von meinen Wünschen statt. Wenn meine Zahlen übereinstimmen, passiert mir das Ereignis "Jackpot gewonnen" (und ich glaube, mein Befehl wurde gehört). Wenn nicht, stelle ich fest, dass mein Befehl ignoriert wurde.

Aus historischer Sicht ist die Lotterie nur an einer Teilmenge von Ereignissen interessiert. Die Lotterie kümmert sich nur darum, dass (a) ein Ticket ausgestellt wurde, (b) die Zahlen ausgewählt wurden und (c) der Jackpot gewonnen wurde. Das sind die Gegenstände von Interesse. Der Kauf des Tickets, der Wunsch zu gewinnen usw. sind irrelevant, ebenso wie das, was ich mit meinem Ticket mache, nachdem ich verloren habe. Während sich die reale Welt für weltliche Ereignisse ändert, müssen wir nur die Ereignisse aufzeichnen, die für unser System von Bedeutung sind.

Theoretisch kann unter einer Ereignisbeschaffungstechnik ein Strom von Ereignissen von Beginn der Zeit an wiedergegeben werden, um zum aktuellen Zustand zu gelangen. Dies beruht auf der Annahme, dass die zugrunde liegenden Systembedingungen konstant und deterministisch sind. Diese Annahmen sind jedoch in vielen Systemen nicht gültig. Die mit einem Ereignis verbundenen Daten sowie die Arten von Ereignissen, an denen wir interessiert sind, können sich im Zuge der Weiterentwicklung unserer Computersoftware ändern. Darüber hinaus kann es rechenintensiv sein, den aktuellen Status als Antwort auf jede Abfrage neu zu berechnen. Aus diesem Grund werden häufig Schnappschüsse des Systemstatus erstellt, um bekannte Zeitpunkte darzustellen, zu denen die neuesten Ereignisse hinzugefügt werden können.

Während es immer noch möglich ist, einen Ereignisstrom über mehrere Versionen hinweg wiederzugeben, ist der damit verbundene menschliche Aufwand wahrscheinlich kostenintensiv. Sofern es keinen berechtigten Grund gibt, diese Funktion in das System zu integrieren, sollten Sie Ihr System besser für die Verwendung von Snapshots erstellen.

Beispiel in Frage

In dem in der Frage gegebenen Beispiel ist die Architektur nicht wirklich ereignisbasiert. es ist befehlsbasiert. Durch die Wiedergabe von Befehlen wird der Systemstatus erstellt. Dies ist ein Anti-Muster und sollte behoben werden. Stattdessen sind die primären Ereignisse:

  • Käufer stellt Frage
  • Verkäufer antwortet auf Frage

Jedes dieser Ereignisse kann "wiedergegeben" werden, um den aktuellen Status anzuzeigen. Wenn Sie beispielsweise eine Frage stellen, besteht das Systemverhalten möglicherweise darin, dem Verkäufer eine E-Mail zu senden und den unanswered questionZähler zu erhöhen . Dieses Verhalten kann geändert werden. Die Tatsache, dass die Frage gestellt wurde, tut dies jedoch nicht. In ähnlicher Weise kann das System den unanswered questionZähler verringern, wenn der Verkäufer antwortet. Dieses Verhalten ist änderbar, die Tatsache, dass der Verkäufer geantwortet hat, jedoch nicht.

Die meisten Event-Sourcing-Systeme berechnen die Anzahl der unbeantworteten Fragen dynamisch, indem sie den spezifischen Ereignisstrom als Antwort auf eine Abfrage wiedergeben.

theMayer
quelle
Dies ist eine großartige Antwort, danke @ rmayer06
Antony Woods
In diesem Lotteriebeispiel sagen Sie, dass "Nur Ereignisse eine Statusänderung im System verursachen können". Wenn das Ereignis jedoch "Ticket ausgestellt" ist (und das Ereignis vermutlich einige Details wie Zeitstempel, Käufer-ID, Ticket-ID enthält), wie geht es Ihnen? Notieren Sie die Referenznummer des Tickets, wenn es kein anderes Aufzeichnungssystem gibt, das die IDs erstellt. Gibt es ein traditionelles CRUD-System, das zuerst ein Ticket erstellen muss, bevor die Ereignisquelle die Tatsache als Vergangenheitsform erfassen kann?
Homan
Die Aktion der Ausstellung des Tickets ist in diesem Fall das Ereignis. Die mit dem Ereignis verbundenen Daten werden in Ihrer Frage als Ereignis bezeichnet, was nützlich, aber technisch nicht korrekt ist. Darüber hinaus stellen Ereignisse typischerweise eine Holarchie von Details dar, bei denen sie in jeder Richtung relativ unbegrenzt zusammengesetzt und zerlegt werden können.
theMayer
Äh ... du hast mich mit dem holarchy Ding umgehauen.
Homan
Ich denke, ich habe über Folgendes nachgedacht: In der CRUD-Welt, insbesondere bei Rails, ist es üblich, IDs für die Primärschlüssel von Tabellen automatisch zu inkrementieren. Wir erstellen Datensätze, ohne die IDs zu kennen. Die DB gibt mir die Ticket-ID zurück. Nach dem, was ich gelesen habe, wird das Ereignis in der Welt der Ereignisbeschaffung "realisiert", bevor es in der Datenbank beibehalten wird, und es erfordert eine aggregierte ID. Anstatt die ID nach der Persistenz von DB zurückzubekommen, muss die eindeutige ID bereits bekannt sein, damit sie als Ganzes beschrieben werden kann. Das scheint, wir sollten immer UUID und keine Auto-IDs machen.
Homan
3

Befehle und Abfragen werden in S3 zum Zweck der Überwachung und Wiederherstellung beibehalten.

Für die Prüfung sicher. Zum Wiederherstellen ? Das ist komisch und verursacht wahrscheinlich Kopfschmerzen.

Wenn Sie Event-Sourcing betreiben möchten, möchten Sie den Status von Ereignissen (Dinge, die in der Vergangenheit passiert sind) und nicht von Befehlen rehydrieren. Dies erspart Ihnen die meisten Probleme, die mit Änderungen an der Befehlsimplementierung verbunden sind. Sie müssen sich nur mit den dauerhaften Statusänderungen befassen.

Die Versionierung ist immer noch ein Problem. Insbesondere möchten Sie sicherstellen, dass Ihre persistierten Ereignisse so geschmeidig wie möglich sind (DTOs-Darstellungen anstelle direkter Serialisierungen der Konzepte in Ihrer Domäne). Wenn Sie Ereignisse aus dem Geschäft lesen, haben Sie die Möglichkeit, sie nach Bedarf zu aktualisieren, bevor Sie sie in den Rehydratisierungszustand versetzen.

VoiceOfUnreason
quelle
Ok, ich denke, Ihr Rat ist, sich weniger Gedanken über das Wiederherstellen von Befehlen als vielmehr über Ereignisse zu machen? Wenn ich beispielsweise den Befehl "10 Beans hinzufügen" erhalte, sollte ich anschließend ein Ereignis ausgeben und speichern , das besagt, dass "10 Beans hinzugefügt wurden. Neue Summe: 40"?
Antony Woods
Ja, das ist richtig. Jede Statusänderung in Ihrer Ereignisquelle wird durch ein oder mehrere Ereignisse dargestellt. Um zu rehydrieren, spielen Sie alle diese Ereignisse der Reihe nach ab.
VoiceOfUnreason
2
Ich habe diese Antwort nicht akzeptiert, aber ich möchte Ihnen für Ihren Beitrag danken, da dies für die Änderung meines Verständnisses von entscheidender Bedeutung war. Ich habe die Antwort von rmayer06 gewählt, nur weil sie direkter, runder und nützlicher für jemanden war, der nur auf diese Frage zugreift, um eine schnelle Antwort zu erhalten.
Antony Woods