Kann ein Befehl in CQRS / ES einen anderen Befehl erstellen?

10

In CQRS / ES wird ein Befehl vom Client an den Server gesendet und an den entsprechenden Befehlshandler weitergeleitet. Dieser Befehlshandler lädt ein Aggregat aus seinem Repository, ruft eine Methode dafür auf und speichert es zurück im Repository. Ereignisse werden generiert. Ein Ereignishandler / Saga / Prozessmanager kann diese Ereignisse abhören, um Befehle auszugeben.

Befehle (Eingabe) erzeugen also Ereignisse (Ausgabe), die dann weitere Befehle (Eingabe) in das System zurückspeisen können. Ist es nun üblich, dass ein Befehl keine Ereignisse ausgibt, sondern einen anderen Befehl in die Warteschlange stellt? Ein solcher Ansatz könnte verwendet werden, um die Ausführung in einem externen Prozess zu erzwingen.

BEARBEITEN:

Der spezifische Anwendungsfall, an den ich denke, ist die Verarbeitung von Zahlungsdetails. Der Client sendet einen PayInvoiceBefehl, dessen Nutzdaten die Kreditkartendaten des Benutzers enthalten. Der PayInvoiceHandlerübergibt einen MakeInvoicePaymentBefehl an einen separaten Prozess, der für die Interaktion mit der Payment - Gateway verantwortlich ist. Wenn die Zahlung erfolgreich ist, wird ein InvoicePaidEreignis generiert. Wenn das System aus irgendeinem Grund abstürzt, nachdem der PayInvoiceBefehl beibehalten wurde, aber bevor der MakeInvoicePaymentBefehl beibehalten wurde, können wir dies manuell verfolgen (es wurde keine Zahlung ausgeführt). Wenn das System abstürzt, nachdem der MakeInvoicePaymentBefehl beibehalten wurde, aber vor demInvoicePaidWenn das Ereignis weiterhin besteht, kann es vorkommen, dass die Kreditkarte des Benutzers belastet wird, die Rechnung jedoch nicht als bezahlt gekennzeichnet wird. In diesem Fall müsste die Situation manuell untersucht und die Rechnung manuell als bezahlt markiert werden.

Magnus
quelle

Antworten:

11

Rückblickend denke ich, dass ich das Problem kompliziert habe.

Im Allgemeinen sollten Befehle entweder eine Ausnahme auslösen oder ein oder mehrere Ereignisse auslösen.

Wenn ich die Architektur von Event Sourcing zusammenfassen könnte, wäre dies wie folgt:

  • Befehle sind Eingaben, die Anweisungen zum Ausführen von Aktionen darstellen.
  • Ereignisse sind Ausgaben, die historische Fakten darüber darstellen, was getan wurde.
  • Ereignishandler können Ereignisse abhören, um Befehle auszugeben, und so die verschiedenen Teile des Systems koordinieren.

Wenn ein Befehl einen anderen Befehl erstellt, führt dies zu Mehrdeutigkeiten in der allgemeinen Bedeutung von Befehlen und Ereignissen: Wenn ein Befehl einen anderen Befehl "auslöst", bedeutet dies, dass ein Befehl "eine historische Tatsache dessen ist, was getan wurde". Dies widerspricht der Absicht dieser beiden Nachrichtentypen und kann zu einem rutschigen Pfad werden, da andere Entwickler Ereignisse aus Ereignissen auslösen können, die zu beschädigten Daten und eventuellen Inkonsistenzen führen können .

In Bezug auf das von mir vorgestellte spezifische Szenario war der komplizierende Faktor, dass ich nicht wollte, dass der Hauptthread mit dem Zahlungsgateway interagiert, da dies (ein dauerhafter Prozess mit einem Thread) die Verarbeitung anderer Befehle nicht zulässt . Die einfache Lösung besteht darin, einen weiteren Thread / Prozess zu erstellen, um die Zahlung abzuwickeln.

Zur Veranschaulichung sendet der Client einen PayInvoiceBefehl. Der PayInvoiceHandlerstartet einen neuen Prozess und übergibt ihm die Zahlungsdetails. Der neue Prozess kommuniziert mit dem Zahlungsgateway. Wenn die Zahlung erfolgreich war, wird invoice.markAsPaid()mit der Quittungsnummer (die das InvoicePaidEreignis erzeugt) angerufen. Wenn die Zahlung nicht erfolgreich war, ruft sie invoice.paymentFailed()mit Passes einen Referenzcode zur weiteren Untersuchung auf (der das InvoicePaymentFailedEreignis erzeugt ). Trotz der Tatsache, dass ein separater Prozess / Thread beteiligt ist, bleibt das Muster bestehen Command -> Event.

In Bezug auf einen Fehler gibt es drei Szenarien: Das System kann abstürzen, nachdem der PayInvoiceBefehl beibehalten wurde, aber bevor die Ereignisse InvoicePaidoder InvoicePaymentFailedbeibehalten werden. In diesem Fall wissen wir nicht, ob die Kreditkarte des Benutzers belastet wurde. In einem solchen Fall wird der Benutzer die Belastung seiner Kreditkarte bemerken und eine Beschwerde einreichen. In diesem Fall kann ein Mitarbeiter das Problem untersuchen und die Rechnung manuell als bezahlt markieren.

Magnus
quelle
1
Vielen Dank für die Frage und die Antwort, gute Denkanstöße. Insgesamt gibt es noch einige Punkte, die ich verwirrend finde: (1) Ich habe nicht viele Leute gesehen, die über den Begriff der Ereignishandler gesprochen haben. (2) Meine Interpretation Ihrer Ansicht verlagert den Großteil der Arbeit auf die Ereignishandler, bis zu dem Punkt, an dem der Befehlshandler zu einer reinen Übersetzungsfunktion wird, und wirft die Frage auf, ob die Trennung in Befehle / Ereignisse überhaupt notwendig ist. Sehen Sie sich diese Folgefrage an, wenn Sie interessiert sind.
bluenote10
3

Sie erhalten ein System, das architektonisch lockerer gekoppelt ist, wenn Sie nur Ereignisse von einem Befehl ausgeben. Anders ausgedrückt, ein Befehl sollte nicht wissen müssen, welche anderen externen Befehle ausgegeben werden sollen. Dies sollte in der Verantwortung der externen Partei liegen (die die Veranstaltung abonnieren sollte und, wie Sie bereits erwähnt haben, ein Saga-Manager mit Koordinierungsverantwortung oder nur ein anderes Modul sein könnte, das von diesen Veranstaltungen abhängig ist).

Erik Eidt
quelle
2

Empfohlene Anzeige: Udi Dahan über zuverlässiges Messaging - es ist nicht ganz das, was Sie beschreiben, aber eng verwandt.

Ist es nun üblich, dass ein Befehl keine Ereignisse ausgibt, sondern einen anderen Befehl in die Warteschlange stellt?

Ich habe niemanden gesehen, der diese Praxis empfohlen hat.

Kurze Antwort: Wenn Sie einen Status nicht speichern, können Sie den Befehl in der Warteschlange nicht wiederherstellen, wenn Sie abstürzen, nachdem Sie bestätigt haben, dass Sie ihn erhalten haben.

Wenn Sie entscheiden, dass Sie den Status speichern müssen, ist es nicht klar, dass es einen großen Vorteil hat, den zweiten Befehl vom ersten zu planen, anstatt einen Ereignishandler zu verwenden.

VoiceOfUnreason
quelle