Ich versuche Wege von DDD und verwandten Themen zu lernen. Ich hatte die Idee eines einfachen begrenzten Kontextes zur Implementierung von "Bank": Es gibt Konten, zwischen denen Geld eingezahlt, abgehoben und übertragen werden kann. Es ist auch wichtig, die Änderungshistorie zu führen.
Ich habe die Account- Entität identifiziert und diese Ereignisbeschaffung wäre gut, um Änderungen daran zu verfolgen. Andere Entitäten oder Wertobjekte sind für das Problem irrelevant, daher werde ich sie nicht erwähnen.
Bei der Betrachtung von Ein- und Auszahlungen ist dies relativ einfach, da nur ein Aggregat geändert wird.
Bei der Übertragung ist es anders - zwei Aggregate müssen durch ein MoneyTransferred- Ereignis geändert werden . DDD lehnt es ab, mehrere Aggregate in einer Transaktion zu ändern. Andererseits besteht die Regel der Ereignisbeschaffung darin, Ereignisse auf Entitäten anzuwenden und den Status basierend auf diesen zu ändern. Wenn das Ereignis einfach in der Datenbank gespeichert werden könnte, gäbe es kein Problem. Um jedoch eine gleichzeitige Änderung von Entitäten aus Ereignisquellen zu verhindern, müssen wir eine Versionierung des Ereignisstroms jedes Aggregats implementieren (um die Transaktionsgrenzen beizubehalten). Mit der Versionierung kommt ein weiteres Problem: Ich kann keine einfachen Strukturen verwenden, um Ereignisse zu speichern und sie zurückzulesen, um sie auf Aggregate anzuwenden.
Meine Frage ist - wie kann ich diese drei Prinzipien zusammenführen: "ein Aggregat eine Transaktion", "Ereignis-> Änderung im Aggregat" und "Verhinderung gleichzeitiger Änderungen"?
quelle
Ein wichtiges Detail für das Verständnis transaktionsbasierter Konten: Das
balance
Attribut vonaccount
ist tatsächlich eine Instanz der Denormalisierung. Es ist da für die Bequemlichkeit. In Wirklichkeit ist der Kontostand die Summe seiner Transaktionen, und Sie benötigen das Konto selbst nicht wirklich , um einen Kontostand zu haben.Vor diesem Hintergrund sollte die Überweisung eines Geldes nicht darin bestehen, es zu aktualisieren,
account
sondern es einzufügentransaction
.Abgesehen davon gibt es eine weitere wichtige Regel: Das Hinzufügen eines
transaction
sollte atomar sein und das (denormalisierte Bilanzfeld von) aktualisierenaccount
.Wenn ich nun das DDD-Konzept von Aggregaten verstehe, scheint Folgendes relevant zu sein:
In Bezug auf das DDD-Design würde ich also vorschlagen:
Es gibt ein Aggregat, das die Übertragung darstellt
Das Aggregat besteht aus folgenden Objekten: der Übertragung (dem Stammobjekt); Das Stammobjekt ist mit zwei Transaktionslisten verknüpft (eine für jedes Konto). und jede Transaktionsliste ist mit einem Konto verknüpft.
Jeder Zugriff auf die Übertragung sollte vom Stammobjekt (the
transfer
) überwacht werden .Wenn Sie versuchen, die Unterstützung für asynchrone Übertragung zu implementieren, sollte sich Ihr Hauptcode nur darum kümmern, die Übertragung in einem "ausstehenden" Status zu erstellen. Möglicherweise haben Sie einen anderen Thread oder einen Job, der das Geld tatsächlich verschiebt (in den Transaktionsverlauf einfügen und daher die Salden aktualisieren) und die Überweisung auf "gebucht" setzt.
Wenn Sie eine in Echtzeit blockierende Übertragungstransaktion implementieren
transfer
möchten, sollte die Geschäftslogik ein erstellen und dieses Objekt würde die anderen Aktivitäten in Echtzeit koordinieren.Um Parallelitätsprobleme zu vermeiden, sollte die erste Aufgabe darin bestehen, die Lastschrifttransaktion in die Transaktionsliste für das Quellkonto einzufügen (natürlich den Saldo aktualisieren). Dies müsste atomar auf Datenbankebene (über eine gespeicherte Prozedur) erfolgen. Nachdem die Belastung erfolgt ist, sollte der Rest der Übertragung unabhängig von Parallelitätsproblemen erfolgreich sein können, da es keine Geschäftsregel geben sollte, die eine Gutschrift auf dem Zielkonto verhindert.
(In der realen Welt haben Bankkonten das Konzept eines Memo-Posts, das das Konzept eines faulen zweiphasigen Commits unterstützt. Die Erstellung des Memo-Posts ist leicht und einfach und kann auch problemlos rückgängig gemacht werden Memo-Post zu einem harten Post ist, wenn sich das Geld tatsächlich bewegt - dies kann nicht zurückgesetzt werden - und stellt die zweite Phase des zweiphasigen Commits dar, die erst erfolgt, nachdem alle Validierungsregeln überprüft wurden.
quelle
Ich bin auch gerade in der Lernphase. Aus Sicht der Implementierung werden Sie diese Aktion meiner Meinung nach auf diese Weise ausführen.
Dispatch TransferMoneyCommand, der folgende Ereignisse auslöst [MoneyTransferEvent, AccountDebitedEvent]
Beachten Sie, dass vor dem Auslösen dieser Ereignisse eine oberflächliche Befehlsüberprüfung und eine Domänenlogiküberprüfung durchgeführt werden müssen, dh hat das Konto einen ausreichenden Kontostand?
Behalten Sie die Ereignisse (mit Versionierung) bei, um sicherzustellen, dass keine Konsistenzprobleme vorliegen. Beachten Sie, dass es einen anderen gleichzeitigen Befehl geben kann (z. B. das Abheben des gesamten Geldes), der erfolgreich war und Ereignisse vor diesem Befehl gespeichert hat. Daher ist der aktuelle Status des Aggregats möglicherweise veraltet und die Ereignisse werden im alten Status ausgelöst und sind falsch. Wenn das Speichern von Ereignissen fehlschlägt, müssen Sie den Befehl von Anfang an wiederholen.
Sobald die Ereignisse erfolgreich in der Datenbank gespeichert wurden, können Sie die beiden Ereignisse veröffentlichen, die ausgelöst wurden.
AccountDebitedEvent entfernt das Geld vom Konto des Zahlungspflichtigen (aktualisiert den Gesamtstatus und alle zugehörigen Ansichts- / Projektionsmodelle).
MoneyTransferEvent startet Saga / Process Manager.
Die Aufgabe des Saga- / Prozessmanagers besteht darin, zu versuchen, das Konto des Zahlungsempfängers gutzuschreiben. Wenn dies fehlschlägt, muss der Restbetrag dem Zahlungsempfänger gutgeschrieben werden.
Saga / Process Manager veröffentlicht einen CreditAccountCommand, der auf das Konto des Zahlungsempfängers angewendet wird und bei Erfolg als AccountCreditedEvent ausgelöst wird.
Wenn Sie diese Aktion aus Sicht der Ereignisbeschaffung umkehren möchten, haben alle Ereignisse in dieser Transaktion die Korrelations- / Kausalitäts-ID als ursprünglichen TransferMoneyCommand, mit der Sie Ereignisse für Rückgängig- / Umkehrvorgänge auslösen können.
Sie können jederzeit Probleme oder mögliche Verbesserungen vorschlagen.
quelle