Bei gegebenem Service A (CMS), der ein Modell steuert (Produkt, nehmen wir an, dass die einzigen Felder ID, Titel, Preis sind) und Services B (Versand) und C (E-Mails), die das gegebene Modell anzeigen müssen, wie der Ansatz aussehen soll bestimmte Modellinformationen über diese Dienste hinweg im Event-Sourcing-Ansatz zu synchronisieren? Nehmen wir an, dass sich der Produktkatalog selten ändert (aber ändert) und dass es Administratoren gibt, die sehr oft auf Daten von Sendungen und E-Mails zugreifen können (Beispielfunktionen sind: B: display titles of products the order contained
und C :) display content of email about shipping that is going to be sent
. Jeder der Dienste verfügt über eine eigene Datenbank.
Lösung 1
Senden Sie alle erforderlichen Informationen zum Produkt innerhalb der Veranstaltung - dies bedeutet folgende Struktur für order_placed
:
{
order_id: [guid],
product: {
id: [guid],
title: 'Foo',
price: 1000
}
}
Bei Service B und C werden Produktinformationen im product
JSON-Attribut in der orders
Tabelle gespeichert
Daher werden zur Anzeige der erforderlichen Informationen nur die vom Ereignis abgerufenen Daten verwendet
Probleme : Abhängig davon, welche anderen Informationen in B und C dargestellt werden müssen, kann die Datenmenge im Ereignisfall zunehmen. B und C erfordern möglicherweise nicht dieselben Informationen zum Produkt, aber das Ereignis muss beide enthalten (es sei denn, wir trennen die Ereignisse in zwei Teile). Wenn gegebenen Daten nicht vorhanden innerhalb der gegebenen Fall ist, Code kann es nicht verwenden - wenn wir eine hinzufügen werden Farbe Option gegeben Produkt, für bestehende Aufträge in B und C, wird gegeben Produkt farblos sein , wenn wir die Ereignisse aktualisieren und sie dann erneut ausführen .
Lösung 2
Senden Sie nur eine Anleitung des Produkts innerhalb der Veranstaltung - dies bedeutet folgende Struktur für order_placed
:
{
order_id: [guid],
product_id: [guid]
}
Bei den Diensten B und C werden die Produktinformationen im product_id
Attribut in der orders
Tabelle gespeichert
Produktinformationen werden von den Diensten B und C bei Bedarf durch Ausführen eines API-Aufrufs an den A/product/[guid]
Endpunkt abgerufen
Probleme : Dies macht B und C (jederzeit) von A abhängig. Wenn sich das Schema des Produkts in A ändert, müssen Änderungen an allen von ihnen abhängigen Diensten (plötzlich) vorgenommen werden.
Lösung 3
Senden Sie nur eine Produktrichtlinie innerhalb des Ereignisses - dies bedeutet die folgende Struktur für order_placed:
{
order_id: [guid],
product_id: [guid]
}
Bei den Diensten B und C werden die Produktinformationen in einer products
Tabelle gespeichert . es gibt noch product_id
auf orders
Tabelle, doch die Replikation von products
Daten zwischen A, B und C; B und C enthalten möglicherweise andere Informationen zum Produkt als A.
Produktinformationen werden beim Erstellen der Services B und C festgelegt und aktualisiert, wenn sich Informationen zu Produkten ändern, indem der A/product
Endpunkt aufgerufen wird (der die erforderlichen Informationen aller Produkte anzeigt) oder indem ein direkter DB-Zugriff auf A durchgeführt und die erforderlichen Produktinformationen kopiert werden Bedienung.
Probleme : Dies macht B und C von A abhängig (beim Säen). Wenn sich das Schema des Produkts in A ändert, müssen Änderungen an allen von ihnen abhängigen Diensten vorgenommen werden (beim Seeding).
Nach meinem Verständnis wäre der richtige Ansatz, Lösung 1 zu verwenden und entweder den Ereignisverlauf gemäß einer bestimmten Logik zu aktualisieren (wenn sich der Produktkatalog nicht geändert hat und wir die anzuzeigende Farbe hinzufügen möchten, können wir den Verlauf sicher aktualisieren, um den aktuellen Status zu erhalten von Produkten und füllen Sie fehlende Daten innerhalb der Ereignisse) oder sorgen Sie für das Nichtvorhandensein bestimmter Daten (wenn sich der Produktkatalog geändert hat und wir die anzuzeigende Farbe hinzufügen möchten, können wir nicht sicher sein, ob zu diesem Zeitpunkt in der Vergangenheit ein bestimmtes Produkt angegeben wurde hatte eine Farbe oder nicht - wir können davon ausgehen, dass alle Produkte im vorherigen Katalog schwarz waren und durch Aktualisierung von Ereignissen oder Code berücksichtigt werden)
quelle
updating event history
- Bei der Beschaffung von Ereignissen ist die Ereignishistorie Ihre Quelle der Wahrheit und sollte niemals geändert werden, sondern nur vorwärts gehen. Wenn sich Ereignisse ändern, können Sie die Ereignisversionierung oder ähnliche Lösungen verwenden. Wenn Sie Ihre Ereignisse jedoch bis zu einem bestimmten Zeitpunkt wiedergeben, sollte der Status der Daten so sein, wie er zu diesem Zeitpunkt war.updating event history
Ich meine: Gehen Sie alle Ereignisse durch und kopieren Sie sie von einem Stream (v1) in einen anderen Stream (v2), um ein konsistentes Ereignisschema beizubehalten.display image at the point when purchase was made
) oder nicht (das die Absicht von darstelltdisplay current image as it within catalog
) vorhanden seinAntworten:
Lösung 3 kommt der richtigen Idee sehr nahe.
Eine Art und Weise zu denken , über diese: B und C jeweils Caching „lokale“ Kopien der Daten , die sie benötigen. Bei B (und ebenfalls bei C) verarbeitete Nachrichten verwenden die lokal zwischengespeicherten Informationen. Ebenso werden Berichte unter Verwendung der lokal zwischengespeicherten Informationen erstellt.
Die Daten werden über eine stabile API von der Quelle in die Caches repliziert. B und C müssen nicht einmal dieselbe API verwenden - sie verwenden das für ihre Anforderungen geeignete Abrufprotokoll. Tatsächlich definieren wir einen Vertrag - Protokoll und Nachrichtenschema -, der den Anbieter und den Verbraucher einschränkt. Dann kann jeder Verbraucher für diesen Vertrag mit jedem Lieferanten verbunden werden. Rückwärts inkompatible Änderungen erfordern einen neuen Vertrag.
Die Dienste wählen die für ihre Anforderungen geeignete Strategie zur Ungültigmachung des Caches aus. Dies kann bedeuten, dass Änderungen regelmäßig aus der Quelle abgerufen werden oder als Reaktion auf eine Benachrichtigung, dass sich die Dinge möglicherweise geändert haben, oder sogar "on demand" - als Durchlesecache fungieren und auf die gespeicherte Kopie der Daten zurückgreifen, wenn Die Quelle ist nicht verfügbar.
Dies gibt Ihnen "Autonomie" in dem Sinne, dass B und C weiterhin Geschäftswert liefern können, wenn A vorübergehend nicht verfügbar ist.
Empfohlene Lektüre: Daten von außen, Daten von innen , Pat Helland 2005.
quelle
In der Informatik gibt es zwei schwierige Dinge , und eine davon ist die Ungültigmachung des Caches.
Lösung 2 ist absolut meine Standardposition, und Sie sollten generell nur in Betracht ziehen, das Caching zu implementieren, wenn Sie auf eines der folgenden Szenarien stoßen:
Leistungsprobleme sind wirklich der Haupttreiber. Es gibt viele Möglichkeiten zur Lösung von Problem Nr. 2, bei denen kein Caching erforderlich ist, z. B. die Sicherstellung, dass Service A hoch verfügbar ist.
Das Zwischenspeichern erhöht die Komplexität eines Systems erheblich und kann zu schwer zu begründenden Randfällen und zu schwer zu replizierenden Fehlern führen. Sie müssen auch das Risiko verringern, veraltete Daten bereitzustellen, wenn neuere Daten vorhanden sind. Dies kann aus geschäftlicher Sicht viel schlimmer sein, als (zum Beispiel) die Meldung anzuzeigen, dass "Service A nicht verfügbar ist - versuchen Sie es später erneut."
Aus diesem ausgezeichneten Artikel von Udi Dahan:
Wenn Sie zu einem bestimmten Zeitpunkt Produktdaten abfragen müssen, sollte dies so gehandhabt werden, wie die Daten in der Produktdatenbank gespeichert sind (z. B. Start- / Enddaten), und in der API deutlich verfügbar gemacht werden (Datum des Inkrafttretens muss erforderlich sein) eine Eingabe für den API-Aufruf sein, um die Daten abzufragen).
quelle
Es ist sehr schwer zu sagen, dass eine Lösung besser ist als die andere. Die Auswahl einer der Lösungen Nr. 2 und Nr. 3 hängt von anderen Faktoren ab (Cache-Dauer, Konsistenztoleranz, ...).
Meine 2 Cent:
Die Ungültigmachung des Caches mag schwierig sein, aber in der Problemstellung wird erwähnt, dass sich der Produktkatalog selten ändert. Diese Tatsache macht Produktdaten zu einem guten Kandidaten für das Caching
Lösung Nr. 1 (NOK)
Lösung 2 (OK)
Lösung Nr. 3 (komplex, aber bevorzugt)
quelle
Im Allgemeinen würde ich Option 2 wegen der zeitlichen Kopplung zwischen diesen beiden Diensten dringend empfehlen (es sei denn, die Kommunikation zwischen diesen Diensten ist sehr stabil und nicht sehr häufig). Zeitliche Kopplung ist das, was Sie beschreiben
this makes B and C dependant upon A (at all times)
, und bedeutet, dass B und C ihre Funktion nicht erfüllen können, wenn A von B oder C aus nicht erreichbar ist.Ich persönlich glaube, dass beide Optionen 1 und 3 Situationen haben, in denen sie gültige Optionen sind.
Wenn die Kommunikation zwischen A und B & C so hoch ist oder die Datenmenge, die für das Ereignis benötigt wird, groß genug ist, um es zu einem Problem zu machen, ist Option 3 die beste Option, da die Belastung des Netzwerks viel geringer ist Die Latenz der Vorgänge nimmt mit abnehmender Nachrichtengröße ab. Weitere hier zu berücksichtigende Bedenken sind:
Option 1 würde ich allerdings nicht ablehnen. Es gibt das gleiche Maß an Kopplung, aber in Bezug auf die Entwicklung sollte es einfach sein (keine besonderen Maßnahmen erforderlich), und die Stabilität der Domäne sollte bedeuten, dass sich diese nicht oft ändern (wie ich bereits erwähnt habe).
Eine andere Option, die ich vorschlagen würde, ist eine geringfügige Abweichung von 3, bei der der Prozess nicht während des Startvorgangs ausgeführt wird, sondern stattdessen ein Ereignis "ProductAdded" und "ProductDetailsChanged" für B und C beobachtet wird, wenn sich der Produktkatalog ändert Dies würde Ihre Bereitstellungen beschleunigen (und es einfacher machen, ein Problem / einen Fehler zu beheben, wenn Sie einen finden).
Bearbeiten 2020-03-03
Ich habe eine bestimmte Reihenfolge von Prioritäten bei der Festlegung des Integrationsansatzes:
Wenn die Kosten für Inkonsistenzen hoch sind (im Grunde müssen die Produktdaten in A so schnell wie möglich mit den in B und C zwischengespeicherten Produkten konsistent sein), können Sie es nicht vermeiden, die Unverfügbarkeit zu akzeptieren und eine synchrone Anfrage zu stellen (wie ein Web) / Rest Request) von B & C nach A, um die Daten abzurufen. Sei vorsichtig! Dies bedeutet immer noch nicht transaktionskonsistent, sondern minimiert nur die Fenster für Inkonsistenzen. Wenn Sie unbedingt sofort konsistent sein müssen, müssen Sie Ihre Servicegrenzen erweitern. Allerdings habe ich sehr fest davon überzeugt , dies kein Problem sein sollte. Aus Erfahrung ist es tatsächlich äußerst selten, dass das Unternehmen einige Sekunden Inkonsistenz nicht akzeptieren kann, sodass Sie nicht einmal synchrone Anforderungen stellen müssen.
Wenn Sie zu bestimmten Zeitpunkten Abfragen benötigen (die ich in Ihrer Frage nicht bemerkt und daher oben möglicherweise nicht berücksichtigt habe, möglicherweise zu Unrecht), sind die Kosten für die Wartung dieser nachgelagerten Dienste so hoch (Sie müssten sie duplizieren interne Ereignisprojektionslogik in allen nachgelagerten Diensten), die die Entscheidung klar macht: Sie sollten das Eigentum A überlassen und A ad-hoc über eine Webanforderung (oder ähnliches) abfragen, und A sollte die Ereignisbeschaffung verwenden, um alle Ereignisse abzurufen, von denen Sie wussten zu der Zeit, um an den Staat zu projizieren und es zurückzugeben. Ich denke, dies ist Option 2 (wenn ich es richtig verstanden habe?), Aber die Kosten sind so hoch, dass die zeitliche Kopplung zwar besser ist als die Wartungskosten für duplizierte Ereignisse und die Projektionslogik.
Wenn Sie keinen Zeitpunkt benötigen und es keinen eindeutigen Eigentümer der Daten gibt (was ich in meiner ersten Antwort aufgrund Ihrer Frage angenommen habe), wäre es ein sehr vernünftiges Muster, Darstellungen zu halten des Produkts in jedem Service separat. Wenn Sie die Daten für Produkte aktualisieren, aktualisieren Sie A, B und C parallel, indem Sie parallele Webanforderungen an jede einzelne senden, oder Sie haben eine Befehls-API, die mehrere Befehle an A, B und C sendet. B & C verwendet deren lokale Version der Daten, um ihre Arbeit zu erledigen, die veraltet sein kann oder nicht. Dies ist keine der oben genannten Optionen (obwohl es nahe an Option 3 liegen könnte), da die Daten in A, B und C unterschiedlich sein können und das "Ganze" des Produkts eine Zusammensetzung aller drei Daten sein kann Quellen.
Zu wissen, ob die Quelle der Wahrheit ein stabiler Vertrag ist, ist nützlich, da Sie damit die Domänen- / internen Ereignisse (oder Ereignisse, die Sie in Ihrer Ereignisbeschaffung als Speichermuster in A speichern) für die Integration zwischen A und den Diensten B und C verwenden können. Wenn der Vertrag stabil ist, können Sie über die Domain-Ereignisse integrieren. Dann haben Sie jedoch ein zusätzliches Problem, wenn häufig Änderungen vorgenommen werden oder der Nachrichtenvertrag groß genug ist, um den Transport zu einem Problem zu machen.
Wenn Sie einen eindeutigen Eigentümer mit einem Vertrag haben, von dem erwartet wird, dass er stabil ist, sind Option 1 die besten Optionen. Eine Bestellung würde alle notwendigen Informationen enthalten und dann würden B und C ihre Funktion unter Verwendung der Daten im Ereignis ausführen.
Wenn sich der Vertrag nach Ihrer Option 3 ändern oder häufig brechen kann, ist es eine bessere Option, auf Webanfragen zum Abrufen von Produktdaten zurückzugreifen, da es viel einfacher ist, mehrere Versionen zu verwalten. Also würde B eine Anfrage für Version 3 des Produkts stellen.
quelle
ProductAdded
oderProductDetailsChanged
um die Komplexität der Verfolgung von Produktkatalogänderungen zu erhöhen, müssen wir diese Daten in irgendeiner Weise zwischen Datenbanken synchronisieren, falls Ereignisse wiedergegeben werden und wir auf Katalogdaten aus der Vergangenheit zugreifen müssen.