Ereignisbeschaffung, ein Ereignis, Status von zwei Aggregaten geändert

10

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"?

Cocsackie
quelle

Antworten:

7

Bei der Übertragung ist es anders - zwei Aggregate müssen durch ein MoneyTransferred-Ereignis geändert werden.

Das Überweisen von Geld ist ein separater Vorgang von der Aktualisierung der Hauptbücher.

MoneyTransferred
AccountCredited
AccountDebited

Die Übung, die dies für mich endgültig gelöst hat, war die Erkenntnis, dass AccountOverdrawnes sich um ein Ereignis handelt. Sie beschreibt den Status des Kontos ohne Rücksicht auf die anderen Teilnehmer an diesem Austausch. Daher muss ein Befehl für ein Konto ausgeführt werden, das das Konto erstellt.

Sie können den Status nicht vernünftigerweise wie AccountOverdrawnaus dem Lesemodell ableiten , da Sie möglicherweise nicht wissen können, ob Sie alle Ereignisse bereits gesehen haben - nur das Aggregat selbst hat zu einem bestimmten Zeitpunkt einen vollständigen Überblick über den Verlauf.

Die Antwort ist natürlich genau dort in der allgegenwärtigen Sprache - Konten werden gutgeschrieben oder belastet, um die Verpflichtungen der Bank gegenüber ihren Kunden widerzuspiegeln.

In Ordnung, aber es bedeutet, dass ich AccountCredited- und AccountDebited-Ereignisse auch für Ein- und Auszahlungen verwenden sollte, sodass ich nur nicht die Ursache der Änderung, sondern die durch eine andere Aktion verursachte Änderung registriere. Wenn ich die Aktion rückgängig machen möchte, konnte ich nicht, da nicht alle Ereignisse registriert sind.

Ich bin mir nicht ganz sicher, ob dies folgt, da Sie (für Fälle wie diesen) eine natürliche Korrelationskennung haben, die die Transaktions-ID selbst ist.

Zweitens - es bedeutet, dass ich so etwas wie Saga verwenden muss.

Etwas andere Schreibweise: Sie brauchen so etwas wie einen Menschen, der die richtigen Befehle sendet .

Es gibt mindestens zwei Möglichkeiten, wie Sie dies tun können. Eine wäre, einen Teilnehmer zu haben MoneyTransferred, der auf die beiden Befehle wartet und diese an die Hauptbücher sendet.

Eine andere Alternative wäre, die Verarbeitung der Transaktion als separates Aggregat zu verfolgen. Stellen Sie sich diese als Checkliste aller Dinge vor, die seit dem Auftreten einer Transaktion erledigt werden müssen. Ein MoneyTransferredEvent-Handler sendet also ProcessTransaction aus, das die auszuführenden Arbeiten plant und überprüft, welche Arbeiten abgeschlossen wurden.

VoiceOfUnreason
quelle
In Ordnung, aber es bedeutet, dass ich AccountCredited- und AccountDebited-Ereignisse auch für Ein- und Auszahlungen verwenden sollte, sodass ich nur nicht die Ursache der Änderung, sondern die durch eine andere Aktion verursachte Änderung registriere. Wenn ich die Aktion rückgängig machen möchte, konnte ich nicht, da nicht alle Ereignisse registriert sind. Wie kann ich das machen (Kausalität von Ereignissen)? Zweitens - es bedeutet, dass ich so etwas wie Saga verwenden muss. Wie soll dann ein Transfer modelliert werden? A in dem Moment habe ich Überweisungsmethode auf Rechnung. Wenn es aufgerufen wird, veröffentlicht es das Ereignis MoneyTransferred . Ich weiß nicht, was so etwas wie eine Saga anfangen soll.
Cocsackie
Ist es nicht -> AccountCredited und AccoundDebited dann MoneyTransferred ? Erste Lösung aktualisiert beiden Aggregate in einer Transaktion (keine Konsistenz Garantie für jede Art)? Es gibt auch kein Aggregat, das MoneyTransferred veröffentlichen könnte -> keine Korrelation. Zweite Lösung scheint besser zu sein - ProcessTransaction veröffentlichen kann MoneyTransferred und mehrere Aggregat Änderung in einer einzigen Transaktion zu vermeiden ich Ereignisse aus veröffentlichen Konto nach Begehung Transaktion. Tut mir leid, dass ich pingelig bin. Für Anfänger ist es schwer zu verstehen - kann nicht nur ein Muster ohne anderes verwenden.
Cocsackie
1

Ein wichtiges Detail für das Verständnis transaktionsbasierter Konten: Das balanceAttribut von accountist 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, accountsondern es einzufügen transaction.

Abgesehen davon gibt es eine weitere wichtige Regel: Das Hinzufügen eines transactionsollte atomar sein und das (denormalisierte Bilanzfeld von) aktualisieren account.

Wenn ich nun das DDD-Konzept von Aggregaten verstehe, scheint Folgendes relevant zu sein:

Das Aggregat ist eine logische Grenze für Dinge, die sich in einem Geschäftsvorgang eines bestimmten Kontexts ändern können. Ein Aggregat kann durch eine einzelne Klasse oder durch eine Vielzahl von Klassen dargestellt werden. Wenn mehr als eine Klasse ein Aggregat darstellt, ist eine davon die sogenannte Stammklasse oder Entität. Der gesamte Zugriff von außen auf das Aggregat muss über die Stammklasse erfolgen.

In Bezug auf das DDD-Design würde ich also vorschlagen:

  1. Es gibt ein Aggregat, das die Übertragung darstellt

  2. 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.

  3. 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 transfermö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.

John Wu
quelle
0

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.

Shayan C.
quelle