Soweit ich weiß, besteht die große Idee hinter CQRS darin, zwei verschiedene Datenmodelle für die Verarbeitung von Befehlen und Abfragen zu haben. Diese werden als "Schreibmodell" und "Lesemodell" bezeichnet.
Betrachten wir ein Beispiel für den Klon einer Twitter-Anwendung. Hier sind die Befehle:
- Benutzer können sich registrieren.
CreateUserCommand(string username)
emittiertUserCreatedEvent
- Benutzer können anderen Benutzern folgen.
FollowUserCommand(int userAId, int userBId)
emittiertUserFollowedEvent
- Benutzer können Beiträge erstellen.
CreatePostCommand(int userId, string text)
emittiertPostCreatedEvent
Während ich oben den Begriff "Ereignis" verwende, meine ich nicht "Ereignisbeschaffungsereignisse". Ich meine nur Signale, die Lesemodellaktualisierungen auslösen. Ich habe keinen Event Store und möchte mich bisher auf CQRS selbst konzentrieren.
Und hier sind die Fragen:
- Ein Benutzer muss die Liste seiner Beiträge sehen.
GetPostsQuery(int userId)
- Ein Benutzer muss die Liste seiner Follower sehen.
GetFollowersQuery(int userId)
- Ein Benutzer muss die Liste der folgenden Benutzer anzeigen.
GetFollowedUsersQuery(int userId)
- Ein Benutzer muss den "Freund-Feed" sehen - ein Protokoll aller Aktivitäten seiner Freunde ("Ihr Freund John hat gerade einen neuen Beitrag erstellt").
GetFriedFeedRecordsQuery(int userId)
Um damit umgehen zu können, muss CreateUserCommand
ich wissen, ob ein solcher Benutzer bereits existiert. An diesem Punkt weiß ich also, dass mein Schreibmodell eine Liste aller Benutzer haben sollte.
Um damit umgehen zu können, muss FollowUserCommand
ich wissen, ob BenutzerA bereits BenutzerB folgt oder nicht. An dieser Stelle möchte ich, dass mein Schreibmodell eine Liste aller Benutzer-folgt-Benutzer-Verbindungen enthält.
Und schließlich CreatePostCommand
glaube ich nicht, dass ich etwas anderes brauche, weil ich keine Befehle wie habe UpdatePostCommand
. Wenn ich diese hätte, müsste ich sicherstellen, dass der Beitrag existiert, also würde ich eine Liste aller Beiträge benötigen. Da ich diese Anforderung nicht habe, muss ich nicht alle Beiträge verfolgen.
Frage 1 : Ist es tatsächlich richtig, den Begriff "Schreibmodell" so zu verwenden, wie ich ihn verwende? Oder steht "Schreibmodell" bei ES immer für "Event Store"? Wenn ja, gibt es eine Trennung zwischen den Daten, die ich für Befehle benötige, und den Daten, die ich für Abfragen benötige?
Um damit fertig zu werden GetPostsQuery
, würde ich eine Liste aller Beiträge benötigen. Dies bedeutet, dass mein Lesemodell eine Liste aller Beiträge enthalten sollte. Ich werde dieses Modell beibehalten, indem ich es mir anhöre PostCreatedEvent
.
Um beides GetFollowersQuery
und zu handhaben GetFollowedUsersQuery
, würde ich eine Liste aller Verbindungen zwischen Benutzern benötigen. Um dieses Modell beizubehalten, werde ich es mir anhören UserFollowedEvent
. Hier ist eine Frage Nr. 2 : Ist es praktisch in Ordnung, wenn ich hier die Verbindungsliste des Schreibmodells verwende? Oder sollte ich besser ein separates Lesemodell erstellen, da ich es in Zukunft möglicherweise mehr Details benötigen muss als das Schreibmodell?
Um damit fertig zu werden, GetFriendFeedRecordsQuery
müsste ich:
- Zuhören
UserFollowedEvent
- Zuhören
PostCreatedEvent
- Wissen, welche Benutzer welchen anderen Benutzern folgen
Wenn Benutzer A Benutzer B folgt und Benutzer B Benutzer C folgt, sollten die folgenden Datensätze angezeigt werden:
- Für Benutzer A: "Ihr Freund Benutzer B hat gerade begonnen, Benutzer C zu folgen."
- Für Benutzer B: "Sie haben gerade begonnen, Benutzer C zu folgen."
- Für Benutzer C: "Benutzer B folgt Ihnen jetzt"
Hier ist die Frage Nr. 3 : Welches Modell soll ich verwenden, um die Liste der Verbindungen zu erhalten? Soll ich ein Schreibmodell verwenden? Soll ich das Lesemodell verwenden - GetFollowersQuery
/ GetFollowedUsersQuery
? Oder sollte ich das GetFriendFeedRecordsQuery
Modell selbst dazu bringen, das zu handhaben UserFollowedEvent
und eine eigene Liste aller Verbindungen zu führen?
quelle
Antworten:
Greg Young (2010)
Wenn Sie in Bezug auf Bertrand Meyers Command Query Separation denken, können Sie sich vorstellen , dass das Modell zwei unterschiedliche Schnittstellen hat, eine, die Befehle unterstützt, und eine, die Abfragen unterstützt.
Greg Youngs Erkenntnis war, dass man dies in zwei getrennte Objekte aufteilen konnte
Nachdem Sie die Objekte getrennt haben, haben Sie jetzt die Möglichkeit, die Datenstrukturen zu trennen, die den Objektstatus im Speicher halten, damit Sie für jeden Fall optimieren können. oder den Lesezustand getrennt vom Schreibzustand speichern / beibehalten.
Unter dem Begriff WriteModel wird normalerweise die veränderbare Darstellung des Modells verstanden (dh das Objekt, nicht der Persistenzspeicher).
TL; DR: Ich denke, Ihre Verwendung ist in Ordnung.
Das ist "gut" - ish. Konzeptionell ist nichts falsch daran, dass das Lese- und Schreibmodell dieselben Strukturen aufweist.
In der Praxis gibt es möglicherweise ein Problem, wenn ein Thread versucht, den Status des Modells zu ändern, während ein zweiter Thread versucht, ihn zu lesen, da Schreibvorgänge in das Modell normalerweise nicht atomar sind.
Die Verwendung des Schreibmodells ist die falsche Antwort.
Das Schreiben mehrerer Lesemodelle, bei denen jedes auf einen bestimmten Anwendungsfall abgestimmt ist, ist völlig sinnvoll. Wir sagen "das Lesemodell", aber es versteht sich, dass es viele Lesemodelle geben kann, von denen jedes für einen bestimmten Anwendungsfall optimiert ist und die Fälle nicht implementiert, in denen es keinen Sinn ergibt.
Sie können beispielsweise einen Schlüsselwertspeicher zur Unterstützung einiger Abfragen und eine Diagrammdatenbank für andere Abfragen oder eine relationale Datenbank verwenden, in der dieses Abfragemodell sinnvoll ist. Pferde für Kurse.
In Ihrem speziellen Fall, in dem Sie das Muster noch lernen, würde ich vorschlagen, Ihr Design "einfach" zu halten - ein Lesemodell zu haben, das nicht die Datenstruktur des Schreibmodells teilt.
quelle
Ich möchte Sie ermutigen zu berücksichtigen, dass Sie ein konzeptionelles Datenmodell haben.
Das Schreibmodell ist dann eine Materialisierung dieses Datenmodells, die für Transaktionsaktualisierungen optimiert ist. Manchmal bedeutet dies eine normalisierte relationale Datenbank.
Das Lesemodell ist eine Materialisierung desselben Datenmodells, das für die Ausführung der Abfragen optimiert ist, die Ihre Anwendung (en) benötigen. Möglicherweise handelt es sich weiterhin um eine relationale Datenbank, die jedoch absichtlich denormalisiert wurde, um Abfragen mit weniger Verknüpfungen zu verarbeiten.
(# 1) Für CQRS muss das Schreibmodell kein Ereignisspeicher sein.
(# 2) Ich würde nicht erwarten, dass das Lesemodell etwas speichert, das nicht im Schreibmodell enthalten ist, zum einen, weil in CQRS niemand das Lesemodell aktualisiert, außer dem Weiterleitungsmechanismus, der das Lesemodell mit den Änderungen am synchronisiert Modell schreiben.
(# 3) In CQRS sollten Abfragen gegen das Lesemodell gerichtet sein. Sie können anders vorgehen: Es ist in Ordnung, CQRS nicht zu folgen.
Zusammenfassend gibt es nur ein konzeptionelles Datenmodell. CQRS trennt das Befehlsnetzwerk und die Funktionen vom Abfragenetzwerk und den Funktionen. Angesichts dieser Trennung können das Schreibmodell und das Lesemodell als Leistungsoptimierung mit sehr unterschiedlichen Technologien gehostet werden.
quelle