In der OOP-Community scheint es weit verbreitete Übereinstimmung zu geben, dass der Klassenkonstruktor ein Objekt nicht teilweise oder sogar vollständig nicht initialisiert lassen sollte.
Was meine ich mit "Initialisierung"? Grob gesagt der atomare Prozess, der ein neu erstelltes Objekt in einen Zustand bringt, in dem alle seine Klasseninvarianten gelten. Es sollte das erste sein, was mit einem Objekt passiert (es sollte nur einmal pro Objekt ausgeführt werden), und es sollte nichts erlaubt sein, an ein nicht initialisiertes Objekt zu gelangen. (Daher der häufige Rat, die Objektinitialisierung direkt im Klassenkonstruktor durchzuführen. Aus dem gleichen Grund werden
Initialize
Methoden häufig verpönt, da diese die Atomizität aufbrechen und es ermöglichen, ein Objekt zu erreichen und zu verwenden, das noch nicht vorhanden ist in einem genau definierten Zustand.)
Problem: Wenn CQRS mit Event Sourcing (CQRS + ES) kombiniert wird, bei dem alle Statusänderungen eines Objekts in einer geordneten Reihe von Ereignissen (Ereignisstrom) erfasst werden, frage ich mich, wann ein Objekt tatsächlich einen vollständig initialisierten Status erreicht: Am Ende des Klassenkonstruktors oder nachdem das allererste Ereignis auf das Objekt angewendet wurde?
Hinweis: Ich verzichte auf die Verwendung des Begriffs "Aggregatwurzel". Wenn Sie es vorziehen, ersetzen Sie es, wenn Sie "Objekt" lesen.
Diskussionsbeispiel: Angenommen, jedes Objekt wird durch einen undurchsichtigen Id
Wert eindeutig identifiziert (denken Sie an die GUID). Ein Ereignisstrom, der die Statusänderungen dieses Objekts darstellt, kann im Ereignisspeicher durch denselben Id
Wert identifiziert werden: (Machen wir uns keine Sorgen über die richtige Ereignisreihenfolge.)
interface IEventStore
{
IEnumerable<IEvent> GetEventsOfObject(Id objectId);
}
Angenommen, es gibt zwei Objekttypen Customer
und ShoppingCart
. Konzentrieren wir uns auf ShoppingCart
: Beim Erstellen sind Einkaufswagen leer und müssen genau einem Kunden zugeordnet werden. Das letzte Bit ist eine Klasseninvariante: Ein ShoppingCart
Objekt, das nicht mit a verknüpft ist, Customer
befindet sich in einem ungültigen Zustand.
In der traditionellen OOP könnte man dies im Konstruktor modellieren:
partial class ShoppingCart
{
public Id Id { get; private set; }
public Customer Customer { get; private set; }
public ShoppingCart(Id id, Customer customer)
{
this.Id = id;
this.Customer = customer;
}
}
Ich bin jedoch ratlos, wie ich dies in CQRS + ES modellieren kann, ohne dass es zu einer verzögerten Initialisierung kommt. Da diese einfache Initialisierung effektiv eine Statusänderung ist, müsste sie nicht als Ereignis modelliert werden?:
partial class CreatedEmptyShoppingCart
{
public ShoppingCartId { get; private set; }
public CustomerId { get; private set; }
}
// Note: `ShoppingCartId` is not actually required, since that Id must be
// known in advance in order to fetch the event stream from the event store.
Dies müsste offensichtlich das allererste Ereignis im Ereignisstrom eines ShoppingCart
Objekts sein, und dieses Objekt würde erst initialisiert, wenn das Ereignis darauf angewendet würde.
Wenn also die Initialisierung Teil der "Wiedergabe" des Ereignisstroms wird (was ein sehr allgemeiner Prozess ist, der wahrscheinlich genauso funktionieren würde, sei es für ein Customer
Objekt oder ein ShoppingCart
Objekt oder einen anderen Objekttyp)…
- Sollte der Konstruktor parameterlos sein und nichts tun und die gesamte Arbeit einer
void Apply(CreatedEmptyShoppingCart)
Methode überlassen (die der verpönten sehr ähnlich istInitialize()
)? - Oder sollte der Konstruktor einen Ereignisstrom empfangen und wiedergeben (was die Initialisierung wieder atomar macht, aber bedeutet, dass der Konstruktor jeder Klasse dieselbe generische "Wiedergabe & Anwenden" -Logik enthält, dh unerwünschte Codeduplizierung)?
- Oder sollte es sein , sowohl ein traditioneller OOP - Konstruktor (wie oben dargestellt) , dass das Objekt korrekt initialisiert, und dann alle Ereignisse , aber die ersten sind
void Apply(…)
-ied darauf?
Ich erwarte keine Antwort auf eine voll funktionsfähige Demo-Implementierung. Ich würde schon sehr freuen, wenn jemand erklären könnte , wo meine Argumentation fehlerhaft ist, oder ob Objektinitialisierung wirklich ist ein „Schmerzpunkt“ in den meisten CQRS + ES - Implementierungen.
Initialize
Methode) besetzt hätten. Das führt mich zu der Frage, wie eine solche Fabrik von Ihnen aussehen könnte.Meiner Meinung nach entspricht die Antwort eher Ihrem Vorschlag Nr. 2 für vorhandene Aggregate. für neue Aggregate eher wie # 3 (aber Verarbeitung des Ereignisses wie von Ihnen vorgeschlagen).
Hier ist ein Code, ich hoffe, er hilft.
quelle
Nun, das erste Ereignis sollte sein, dass ein Kunde einen Warenkorb erstellt . Wenn der Warenkorb erstellt wird, haben Sie bereits eine Kunden-ID als Teil des Ereignisses.
Wenn ein Zustand zwischen zwei verschiedenen Ereignissen besteht, ist er per Definition ein gültiger Zustand. Wenn Sie also sagen, dass einem Kunden ein gültiger Warenkorb zugeordnet ist, bedeutet dies, dass Sie die Kundeninformationen benötigen, wenn Sie den Warenkorb selbst erstellen
quelle
CreatedEmptyShoppingCart
Veranstaltung in meiner Frage an: Sie enthält die Kundeninformationen, genau wie Sie es vorschlagen. Meine Frage bezieht sich eher darauf, wie sich ein solches Ereignis auf den KlassenkonstruktorShoppingCart
während der Implementierung bezieht oder mit diesem konkurriert .Hinweis: Wenn Sie kein aggregiertes Stammverzeichnis verwenden möchten, umfasst "Entität" das meiste, worüber Sie hier fragen, während Sie die Bedenken hinsichtlich der Transaktionsgrenzen umgehen.
Hier ist eine andere Art, darüber nachzudenken: Eine Entität ist Identität + Zustand. Im Gegensatz zu einem Wert ist eine Entität derselbe Typ, selbst wenn sich sein Zustand ändert.
Aber der Staat selbst kann als Wertobjekt betrachtet werden. Damit meine ich, der Staat ist unveränderlich; Die Historie der Entität ist ein Übergang von einem unveränderlichen Zustand zum nächsten - jeder Übergang entspricht einem Ereignis im Ereignisstrom.
Die onEvent () -Methode ist natürlich eine Abfrage - currentState wird überhaupt nicht geändert, stattdessen berechnet currentState die Argumente, die zum Erstellen des nextState verwendet werden.
Nach diesem Modell kann davon ausgegangen werden, dass alle Instanzen des Einkaufswagens mit demselben Startwert beginnen.
Trennung von Bedenken - Der ShoppingCart verarbeitet einen Befehl, um herauszufinden, welches Ereignis als nächstes kommen soll. Der ShoppingCart-Status weiß, wie er zum nächsten Status gelangt.
quelle