CQRS Event Sourcing: Überprüfen Sie die Eindeutigkeit des Benutzernamens

75

Nehmen wir ein einfaches Beispiel für die "Kontoregistrierung". Hier ist der Ablauf:

  • Benutzer besuchen Website
  • Klicken Sie auf die Schaltfläche "Registrieren" und füllen Sie das Formular aus. Klicken Sie auf die Schaltfläche "Speichern"
  • MVC Controller: Überprüfen Sie die Eindeutigkeit von Benutzernamen, indem Sie aus ReadModel lesen
  • RegisterCommand: Überprüfen Sie die Eindeutigkeit des Benutzernamens erneut (hier ist die Frage).

Natürlich können wir die Eindeutigkeit von Benutzernamen überprüfen, indem wir von ReadModel in MVC Controller lesen, um die Leistung und Benutzererfahrung zu verbessern. Allerdings müssen wir noch die Einzigartigkeit wieder in RegisterCommand zu validieren , und natürlich sollten wir nicht den Zugang ReadModel in Befehle.

Wenn wir Event Sourcing nicht verwenden, können wir das Domänenmodell abfragen, sodass dies kein Problem darstellt. Wenn wir jedoch Event Sourcing verwenden, können wir das Domänenmodell nicht abfragen. Wie können wir also die Eindeutigkeit von Benutzernamen in RegisterCommand überprüfen?

Hinweis: Die Benutzerklasse verfügt über eine Id-Eigenschaft, und UserName ist nicht die Schlüsseleigenschaft der User-Klasse. Wir können das Domänenobjekt nur bei Verwendung der Ereignisbeschaffung anhand der ID abrufen.

Übrigens: Wenn der eingegebene Benutzername bereits vergeben ist, sollte auf der Website dem Besucher die Fehlermeldung "Entschuldigung, der Benutzername XXX ist nicht verfügbar" angezeigt werden. Es ist nicht akzeptabel, dem Besucher eine Nachricht mit der Aufschrift "Wir erstellen Ihr Konto, bitte warten Sie, wir senden Ihnen das Registrierungsergebnis später per E-Mail" zu zeigen.

Irgendwelche Ideen? Danke vielmals!

[AKTUALISIEREN]

Ein komplexeres Beispiel:

Anforderung:

Wenn Sie eine Bestellung aufgeben, sollte das System die Bestellhistorie des Kunden überprüfen. Wenn er ein wertvoller Kunde ist (wenn der Kunde im letzten Jahr mindestens 10 Bestellungen pro Monat aufgegeben hat, ist er wertvoll), erhalten Sie 10% Rabatt auf die Bestellung.

Implementierung:

Wir erstellen PlaceOrderCommand und müssen im Befehl den Bestellverlauf abfragen, um festzustellen, ob der Client wertvoll ist. Aber wie können wir das machen? Wir sollten nicht im Befehl auf ReadModel zugreifen! Wie Mikael sagte , können wir im Beispiel für die Kontoregistrierung Kompensationsbefehle verwenden. Wenn wir diese jedoch auch in diesem Bestellbeispiel verwenden, wäre dies zu komplex und der Code möglicherweise zu schwierig zu pflegen.

Mouhong Lin
quelle

Antworten:

37

Wenn Sie den Benutzernamen anhand des Lesemodells überprüfen, bevor Sie den Befehl senden, handelt es sich um ein Race-Bedingungsfenster von einigen hundert Millisekunden, in dem eine echte Race-Bedingung auftreten kann, die in meinem System nicht behandelt wird. Es ist einfach zu unwahrscheinlich, dass dies im Vergleich zu den Kosten für den Umgang damit passiert.

Wenn Sie jedoch das Gefühl haben, aus irgendeinem Grund damit umgehen zu müssen, oder wenn Sie nur das Gefühl haben, wissen zu wollen, wie Sie einen solchen Fall meistern können, haben Sie folgende Möglichkeiten:

Sie sollten weder über den Befehlshandler noch über die Domäne auf das Lesemodell zugreifen, wenn Sie die Ereignisbeschaffung verwenden. Sie können jedoch einen Domänendienst verwenden, der das UserRegistered-Ereignis abhört, bei dem Sie erneut auf das Lesemodell zugreifen und prüfen, ob der Benutzername immer noch kein Duplikat ist. Natürlich müssen Sie hier die UserGuid verwenden, und Ihr Lesemodell wurde möglicherweise mit dem gerade erstellten Benutzer aktualisiert. Wenn ein Duplikat gefunden wird, haben Sie die Möglichkeit, Ausgleichsbefehle zu senden, z. B. den Benutzernamen zu ändern und den Benutzer darüber zu informieren, dass der Benutzername verwendet wurde.

Das ist eine Herangehensweise an das Problem.

Wie Sie wahrscheinlich sehen können, ist dies nicht synchron auf Anfrage-Antwort-Weise möglich. Um dies zu lösen, verwenden wir SignalR, um die Benutzeroberfläche zu aktualisieren, wenn etwas an den Client gesendet werden soll (sofern diese noch verbunden sind). Wir lassen den Webclient Ereignisse abonnieren, die Informationen enthalten, die für den Client sofort sichtbar sind.

Aktualisieren

Für den komplexeren Fall:

Ich würde sagen, dass die Auftragserteilung weniger komplex ist, da Sie das Lesemodell verwenden können, um herauszufinden, ob der Client wertvoll ist, bevor Sie den Befehl senden. Tatsächlich können Sie dies beim Laden des Bestellformulars abfragen, da Sie dem Kunden wahrscheinlich zeigen möchten, dass er die 10% Rabatt erhält, bevor er die Bestellung aufgibt. Fügen Sie einfach einen Rabatt zum PlaceOrderCommandund möglicherweise einen Grund für den Rabatt hinzu, damit Sie nachverfolgen können, warum Sie Gewinne kürzen.

Wenn Sie jedoch aus irgendeinem Grund den Rabatt wirklich berechnen müssen, nachdem die Bestellung aufgegeben wurde, verwenden Sie erneut einen Domain-Service, der abhört, OrderPlacedEventund der Befehl "Kompensieren" wäre in diesem Fall wahrscheinlich ein DiscountOrderCommandoder etwas anderes. Dieser Befehl wirkt sich auf das Stammverzeichnis des Auftragsaggregats aus, und die Informationen können an Ihre Lesemodelle weitergegeben werden.

Für den Fall des doppelten Benutzernamens:

Sie können einen ChangeUsernameCommandals Ausgleichsbefehl vom Domänendienst senden . Oder sogar etwas Spezifischeres, das den Grund beschreibt, warum sich der Benutzername geändert hat, was auch zur Erstellung eines Ereignisses führen kann, das der Webclient abonnieren kann, damit Sie dem Benutzer zeigen können, dass der Benutzername ein Duplikat ist.

Im Zusammenhang mit dem Domänendienst würde ich sagen, dass Sie auch die Möglichkeit haben, den Benutzer auf andere Weise zu benachrichtigen, z. B. durch Senden einer E-Mail, die nützlich sein kann, da Sie nicht wissen können, ob der Benutzer noch verbunden ist. Möglicherweise könnte diese Benachrichtigungsfunktion durch dasselbe Ereignis ausgelöst werden, das der Webclient abonniert.

Wenn es um SignalR geht, verwende ich einen SignalR-Hub, mit dem die Benutzer eine Verbindung herstellen, wenn sie ein bestimmtes Formular laden. Ich verwende die SignalR Group-Funktionalität, mit der ich eine Gruppe erstellen kann, die den Wert der Guid benennt, die ich im Befehl sende. Dies könnte in Ihrem Fall die userGuid sein. Dann habe ich Eventhandler, der Ereignisse abonniert, die für den Client nützlich sein könnten, und wenn ein Ereignis eintrifft, kann ich auf allen Clients in der SignalR-Gruppe eine Javascript-Funktion aufrufen (in diesem Fall nur der eine Client, der den doppelten Benutzernamen in Ihrem erstellt Fall). Ich weiß, es klingt komplex, ist es aber nicht. Ich hatte alles an einem Nachmittag eingerichtet. Auf der SignalR Github-Seite finden Sie großartige Dokumente und Beispiele.

Mikael Östberg
quelle
Was soll ich im Kompensationsbefehl tun, wenn ich feststelle, dass der Benutzername doppelt vorhanden ist? Ein SignalR-Ereignis veröffentlichen, um den Client darüber zu informieren, dass der Benutzername nicht verfügbar ist? (Ich habe SignalR nicht benutzt, ich denke, es könnte irgendeine Art von "Ereignissen" geben?)
Mouhong Lin
1
Ich denke, wir haben es Application Service in DDD genannt, aber ich könnte mich irren. Außerdem ist Domain Service ein umstrittener Begriff in der DDDD / CQRS-Community. Was Sie jedoch brauchen, ist etwas Ähnliches wie das, was sie Saga nennen, außer dass Sie wahrscheinlich weder einen Zustand noch eine Zustandsmaschine benötigen. Sie brauchen nur etwas, das reagieren und von Ereignissen profitieren kann, Daten nachschlagen und Befehle senden kann. Ich nenne sie Domain-Dienste. Kurz gesagt, Sie abonnieren Ereignisse und senden Befehle. Dies ist auch bei der Kommunikation zwischen Aggregate Roots hilfreich.
Mikael Östberg
1
Ich sollte auch erwähnen, dass ich meine Domänendienste in einem völlig anderen Prozess habe, der zum Beispiel von den Lesemodellen getrennt ist. Dies vereinfacht die Handhabung von Nachrichten, wie Abonnements und dergleichen.
Mikael Östberg
1
Dies ist eine großartige Antwort. Ich sehe diesen Kommentar jedoch häufig. "Sie sollten nicht über den Befehlshandler oder die Domäne auf das Lesemodell zugreifen, wenn Sie Event Sourcing verwenden." Kann jemand erklären, warum es so eine schlechte Idee ist, das Lesemodell von der Befehls- / Domänenseite aus zu verwenden. Ist dies der Punkt der Befehls- / Abfragetrennung?
Scott Coates
1
Die Kombination aus Domänenstatus und Befehl muss für die Entscheidung ausreichen. Wenn Sie der Meinung sind, dass Sie bei der Verarbeitung von Befehlen Daten lesen müssen, bringen Sie diese Daten im Befehl mit oder speichern Sie sie im Domänenstatus. Und warum? - Der Lesespeicher ist letztendlich konsistent, er hat möglicherweise nicht die Wahrheit. Der Domänenstatus ist die Wahrheit und der Befehl vervollständigt ihn. - Wenn Sie ES verwenden, können Sie den Befehl zusammen mit den Ereignissen speichern. Auf diese Weise sehen Sie genau, auf welche Informationen Sie reagiert haben. - Wenn Sie vorher gelesen haben, können Sie eine Validierung durchführen und die Erfolgswahrscheinlichkeit Ihres Befehls erhöhen.
Mikael Östberg
24

Ich denke, Sie müssen noch die Denkweise auf eventuelle Konsistenz und die Art der Event-Beschaffung umstellen. Ich hatte das gleiche Problem. Insbesondere habe ich mich geweigert zu akzeptieren, dass Sie Befehlen des Kunden vertrauen sollten, die in Ihrem Beispiel "Diese Bestellung mit 10% Rabatt aufgeben" sagen, ohne dass die Domain bestätigt, dass der Rabatt erfolgen soll. Eine Sache, die mich wirklich beeindruckt hat, hat Udi selbst zu mir gesagt (siehe Kommentare der akzeptierten Antwort).

Grundsätzlich wurde mir klar, dass es keinen Grund gibt, dem Kunden nicht zu vertrauen. Alles auf der Leseseite wurde aus dem Domänenmodell erstellt, daher gibt es keinen Grund, die Befehle nicht zu akzeptieren. Was auch immer auf der Leseseite steht, dass der Kunde für einen Rabatt qualifiziert ist, wurde von der Domain dort angegeben.

Übrigens: Wenn der eingegebene Benutzername bereits vergeben ist, sollte auf der Website dem Besucher die Fehlermeldung "Entschuldigung, der Benutzername XXX ist nicht verfügbar" angezeigt werden. Es ist nicht akzeptabel, dem Besucher eine Nachricht mit der Aufschrift "Wir erstellen Ihr Konto, bitte warten Sie, wir senden Ihnen das Registrierungsergebnis später per E-Mail" zu zeigen.

Wenn Sie Event Sourcing und eventuelle Konsistenz übernehmen möchten, müssen Sie akzeptieren, dass es manchmal nicht möglich ist, Fehlermeldungen sofort nach dem Senden eines Befehls anzuzeigen. Mit dem Beispiel für einen eindeutigen Benutzernamen sind die Chancen, dass dies geschieht, so gering (vorausgesetzt, Sie überprüfen die Leseseite, bevor Sie den Befehl senden), dass es sich nicht lohnt, sich über zu viele Gedanken zu machen, aber für dieses Szenario müsste eine nachfolgende Benachrichtigung gesendet werden, oder fragen Sie vielleicht sie für einen anderen Benutzernamen, wenn sie sich das nächste Mal anmelden. Das Tolle an diesen Szenarien ist, dass Sie über den Geschäftswert nachdenken und darüber, was wirklich wichtig ist.

UPDATE: Okt. 2015

Ich wollte nur hinzufügen, dass tatsächlich, wenn es um öffentlich zugängliche Websites geht, die Angabe, dass eine E-Mail bereits vergeben ist, tatsächlich gegen bewährte Sicherheitsmethoden verstößt. Stattdessen sollte die Registrierung erfolgreich abgeschlossen worden sein und den Benutzer darüber informiert haben, dass eine Bestätigungs-E-Mail gesendet wurde. Wenn jedoch der Benutzername vorhanden ist, sollte die E-Mail ihn darüber informieren und ihn auffordern, sich anzumelden oder sein Kennwort zurückzusetzen. Dies funktioniert zwar nur, wenn E-Mail-Adressen als Benutzername verwendet werden, was ich aus diesem Grund für ratsam halte.

David Masters
quelle
3
Hervorragender Input. Es ist der Geist, der sich ändern muss, bevor das System es kann (ich hatte nicht vor, dort wie Yoda zu klingen).
Mikael Östberg
5
+1 Nur hier wirklich pedantisch zu sein ... ES & EC sind zwei völlig verschiedene Dinge und die Verwendung einer sollte nicht die Verwendung der anderen bedeuten (obwohl dies in den meisten Fällen durchaus sinnvoll ist). Es ist absolut gültig, ES zu verwenden, ohne ein eventuell konsistentes Modell zu haben und umgekehrt.
James
"Grundsätzlich wurde mir klar, dass es keinen Grund gibt, dem Kunden nicht zu vertrauen" - ja, ich denke, das ist ein fairer Kommentar. Aber wie geht man mit externem Zugriff um, der möglicherweise Befehle erzeugt? Natürlich möchten wir keinen PlaceOrderCommand mit einem Rabatt zulassen, der automatisch angewendet wird. Die Anwendung eines Rabatts ist eine Domain-Logik, nicht etwas, dem wir jemandem "vertrauen" können, der uns auffordert, uns zu bewerben.
Stephen Drew
3
@StephenDrew - Client bedeutet in diesem Zusammenhang nur, welche Codeeinheit den Befehl erzeugt. Sie können (und sollten vielleicht) eine Schicht vor dem Befehlsbus haben. Wenn Sie einen externen Webdienst erstellen, führt der MVC-Controller, der eine Bestellung aufgibt, zuerst die Abfrage aus und sendet dann den Befehl. Der Client hier ist Ihr Controller.
Ryeguy
5
Wenn Sie Ihre Antwort genau zu Herzen nehmen, bedeutet dies, dass die gesamte Theorie zu "Invarianten", "Geschäftsregeln" und "Hohe Verkapselung" absoluter Unsinn ist. Es gibt zu viele Gründe, der Benutzeroberfläche nicht zu vertrauen. Und schließlich ist die Benutzeroberfläche kein obligatorischer Bestandteil ... was ist, wenn es keine Benutzeroberfläche gibt?
Cristian E.
18

Es ist nichts Falsches daran, einige sofort konsistente Lesemodelle zu erstellen (z. B. nicht über ein verteiltes Netzwerk), die in derselben Transaktion wie der Befehl aktualisiert werden.

Wenn Lesemodelle möglicherweise über ein verteiltes Netzwerk konsistent sind, wird die Skalierung des Lesemodells für schwere Lesesysteme unterstützt. Es gibt jedoch nichts zu sagen, dass Sie kein domänenspezifisches Lesemodell haben können, das sofort konsistent ist.

Das sofort konsistente Lesemodell wird immer nur zum Überprüfen von Daten verwendet, bevor ein Befehl ausgegeben wird. Sie sollten es niemals zum direkten Anzeigen von Lesedaten für einen Benutzer verwenden (dh aus einer GET-Webanforderung oder ähnlichem). Verwenden Sie dazu eventuell konsistente, skalierbare Lesemodelle.

Gaz_Edge
quelle
9

In Bezug auf die Einzigartigkeit habe ich Folgendes implementiert:

  • Ein erster Befehl wie "StartUserRegistration". UserAggregate wird unabhängig davon erstellt, ob der Benutzer eindeutig ist oder nicht, jedoch mit dem Status RegistrationRequested.

  • Bei "UserRegistrationStarted" wird eine asynchrone Nachricht an einen zustandslosen Dienst "UsernamesRegistry" gesendet. wäre so etwas wie "RegisterName".

  • Der Dienst würde versuchen, die Tabelle zu aktualisieren (keine Abfragen, "Tell Don't Ask"), die eine eindeutige Einschränkung enthalten würde.

  • Bei Erfolg antwortet der Dienst mit einer anderen Nachricht (asynchron) mit einer Art Autorisierung "UsernameRegistration", die besagt, dass der Benutzername erfolgreich registriert wurde. Sie können eine requestId einfügen, um bei gleichzeitiger Kompetenz den Überblick zu behalten (unwahrscheinlich).

  • Der Aussteller der obigen Nachricht hat jetzt die Berechtigung, dass der Name selbst registriert wurde, sodass das UserRegistration-Aggregat jetzt sicher als erfolgreich markiert werden kann. Andernfalls als verworfen markieren.

Zusammenfassung:

  • Dieser Ansatz beinhaltet keine Abfragen.

  • Die Benutzerregistrierung wird immer ohne Validierung erstellt.

  • Der Bestätigungsprozess würde zwei asynchrone Nachrichten und eine Datenbankeinfügung umfassen. Die Tabelle ist nicht Teil eines Lesemodells, sondern eines Dienstes.

  • Schließlich ein asynchroner Befehl, um zu bestätigen, dass der Benutzer gültig ist.

  • Zu diesem Zeitpunkt könnte ein Denormalisierer auf ein UserRegistrationConfirmed-Ereignis reagieren und ein Lesemodell für den Benutzer erstellen.

Daniel Vasquez
quelle
2
Ich mache etwas Ähnliches. In meinem Event-Sourcing-System habe ich ein UserName-Aggregat. AggregateID ist der Benutzername, den ich registrieren möchte. Ich gebe einen Befehl aus, um ihn zu registrieren. Wenn es bereits registriert ist, erhalten wir eine Veranstaltung. Wenn es verfügbar ist, wird es sofort registriert und wir erhalten eine Veranstaltung. Ich versuche, "Dienste" zu vermeiden, da sie manchmal das Gefühl haben, dass in der Domäne ein Modellierungsfehler vorliegt. Indem wir einen Benutzernamen zu einem erstklassigen Aggregat machen, modellieren wir die Einschränkung in der Domäne.
CPerson
7

Ich denke, in solchen Fällen können wir einen Mechanismus wie "Beratungssperre mit Ablauf" verwenden.

Beispielausführung:

  • Überprüfen Sie, ob der Benutzername im eventuell konsistenten Lesemodell vorhanden ist oder nicht
  • Wenn nicht vorhanden; durch Verwendung einer Redis-Couchbase wie Keyvalue Storage oder Cache; Versuchen Sie, den Benutzernamen als Schlüsselfeld mit einem gewissen Ablaufdatum zu verschieben.
  • Falls erfolgreich; Erhöhen Sie dann userRegisteredEvent.
  • Wenn entweder ein Benutzername im Lesemodell oder im Cache-Speicher vorhanden ist, informieren Sie den Besucher darüber, dass der Benutzername verwendet wurde.

Sogar Sie können eine SQL-Datenbank verwenden; Geben Sie den Benutzernamen als Primärschlüssel für eine Sperrtabelle ein. und dann kann ein geplanter Job Ablaufzeiten verarbeiten.

Safak Ulusoy
quelle
7

Wie viele andere sind wir bei der Implementierung eines ereignisbasierten Systems auf das Eindeutigkeitsproblem gestoßen.

Zuerst habe ich den Client vor dem Senden eines Befehls auf die Abfrageseite zugreifen lassen, um herauszufinden, ob ein Benutzername eindeutig ist oder nicht. Aber dann habe ich festgestellt, dass es eine schlechte Idee ist, ein Back-End zu haben, das keine Validierung der Eindeutigkeit aufweist. Warum überhaupt etwas erzwingen, wenn es möglich ist, einen Befehl zu veröffentlichen, der das System beschädigen würde? Ein Back-End sollte alle Eingaben überprüfen, sonst sind Sie offen für inkonsistente Daten.

Wir haben eine indexTabelle auf der Befehlsseite erstellt. Im einfachen Fall eines Benutzernamens, der eindeutig sein muss, erstellen Sie einfach eine Tabelle user_name_index, die die Felder enthält, die eindeutig sein müssen. Jetzt kann die Befehlsseite die Eindeutigkeit eines Benutzernamens abfragen. Nachdem der Befehl ausgeführt wurde, ist es sicher, den neuen Benutzernamen im Index zu speichern.

So etwas könnte auch für das Problem des Bestellrabattes funktionieren.

Die Vorteile sind, dass Ihr Befehls-Backend alle Eingaben ordnungsgemäß überprüft, sodass keine inkonsistenten Daten gespeichert werden können.

Ein Nachteil könnte sein, dass Sie für jede Eindeutigkeitsbeschränkung eine zusätzliche Abfrage benötigen und zusätzliche Komplexität erzwingen.

Jonas Geiregat
quelle
2

Haben Sie darüber nachgedacht, einen "funktionierenden" Cache als eine Art RSVP zu verwenden? Es ist schwer zu erklären, da es in einem kleinen Zyklus funktioniert. Wenn jedoch ein neuer Benutzername "beansprucht" wird (dh der Befehl wurde erteilt, um ihn zu erstellen), platzieren Sie den Benutzernamen mit einem kurzen Ablauf im Cache ( lang genug, um eine weitere Anforderung zu berücksichtigen, die durch die Warteschlange gelangt und in das Lesemodell denormalisiert wird). Wenn es sich um eine Dienstinstanz handelt, würde der Speicher wahrscheinlich funktionieren, andernfalls wird er mit Redis oder Ähnlichem zentralisiert.

Während der nächste Benutzer das Formular ausfüllt (vorausgesetzt, es gibt ein Front-End), überprüfen Sie asynchron das Lesemodell auf Verfügbarkeit des Benutzernamens und benachrichtigen den Benutzer, wenn es bereits vergeben ist. Wenn der Befehl gesendet wird, überprüfen Sie den Cache (nicht das Lesemodell), um die Anforderung zu validieren, bevor Sie den Befehl annehmen (bevor Sie 202 zurückgeben). Wenn sich der Name im Cache befindet, akzeptieren Sie den Befehl nicht. Wenn dies nicht der Fall ist, fügen Sie ihn dem Cache hinzu. Wenn das Hinzufügen fehlschlägt (doppelter Schlüssel, weil Sie von einem anderen Prozess geschlagen wurden), nehmen Sie an, dass der Name vergeben ist, und antworten Sie dem Client entsprechend. Ich glaube nicht, dass zwischen den beiden Dingen viel Gelegenheit für eine Kollision besteht.

Wenn es kein Front-End gibt, können Sie die asynchrone Suche überspringen oder zumindest Ihre API den Endpunkt für die Suche bereitstellen lassen. Sie sollten dem Client sowieso nicht erlauben, direkt mit dem Befehlsmodell zu sprechen, und wenn Sie eine API davor platzieren, können Sie die API als Vermittler zwischen dem Befehl und den gelesenen Hosts einsetzen.

Sinaesthetic
quelle
2

Es scheint mir, dass das Aggregat hier vielleicht falsch ist.

Wenn Sie im Allgemeinen sicherstellen müssen, dass der zu Y gehörende Wert Z innerhalb der Menge X eindeutig ist, verwenden Sie X als Aggregat. X ist schließlich der Ort, an dem die Invariante tatsächlich existiert (nur ein Z kann in X sein).

Mit anderen Worten, Ihre Invariante ist, dass ein Benutzername möglicherweise nur einmal im Bereich aller Benutzer Ihrer Anwendung angezeigt wird (oder ein anderer Bereich sein kann, z. B. innerhalb einer Organisation usw.), wenn Sie einen aggregierten "ApplicationUsers" haben und senden Wenn Sie den Befehl "RegisterUser" verwenden, sollten Sie in der Lage sein, über das zu verfügen, was Sie benötigen, um sicherzustellen, dass der Befehl gültig ist, bevor Sie das Ereignis "UserRegistered" speichern. (Und natürlich können Sie dieses Ereignis dann verwenden, um die Projektionen zu erstellen, die Sie benötigen, um beispielsweise den Benutzer zu authentifizieren, ohne das gesamte Aggregat "ApplicationUsers" laden zu müssen.

John Wilger
quelle
Genau so muss man über Aggregate denken. Der Zweck eines Aggregats besteht darin, sich vor Parallelität / Inkonsistenz zu schützen (Sie müssen dies durch einen Mechanismus garantieren, damit es ein Aggregat ist). Wenn Sie so über sie nachdenken, erkennen Sie auch die Kosten für den Schutz der Invarianten. Im schlimmsten Fall in einem höchst umstrittenen System müssten alle Nachrichten an das Aggregat von einem einzigen Prozess serialisiert und verarbeitet werden. Widerspricht dies der Skala, in der Sie arbeiten? In diesem Fall sollten Sie den Wert der Invariante überdenken.
Andrew Larsson
2
Für dieses spezielle Szenario mit Benutzernamen können Sie dennoch eine Eindeutigkeit erzielen, während Sie horizontal skalierbar sind. Sie können Ihre Benutzernamenregistrierungsaggregate entlang der ersten N Zeichen des Benutzernamens partitionieren. Wenn Sie beispielsweise Tausende von gleichzeitigen Registrierungen verarbeiten müssen, partitionieren Sie entlang der ersten drei Buchstaben des Benutzernamens. Um sich für den Benutzernamen "johnwilger123" zu registrieren, adressieren Sie die Nachricht an die Aggregatinstanz mit der ID "joh" und sie kann die Menge aller "joh" -Nutzernamen auf Eindeutigkeit überprüfen.
Andrew Larsson