Es scheint endlose Verwirrung darüber zu geben, ob Befehle Rückgabewerte haben sollten oder nicht. Ich würde gerne wissen, ob die Verwirrung einfach darauf zurückzuführen ist, dass die Teilnehmer ihren Kontext oder ihre Umstände nicht angegeben haben.
Die Verwirrung
Hier sind Beispiele für die Verwirrung ...
Udi Dahan sagt, dass Befehle "keine Fehler an den Client zurückgeben", aber im selben Artikel zeigt er ein Diagramm, in dem Befehle tatsächlich Fehler an den Client zurückgeben.
In einem Artikel im Microsoft Press Store heißt es: "Der Befehl ... gibt keine Antwort zurück", gibt dann jedoch eine mehrdeutige Warnung:
Da die Erfahrung auf dem Schlachtfeld um CQRS wächst, konsolidieren sich einige Praktiken und werden tendenziell zu Best Practices. Teilweise im Gegensatz zu dem, was wir gerade gesagt haben ... ist es heutzutage eine verbreitete Ansicht, dass sowohl der Befehlshandler als auch die Anwendung wissen müssen, wie die Transaktionsoperation verlaufen ist. Ergebnisse müssen bekannt sein ...
- Jimmy Bogard sagt, " Befehle haben immer ein Ergebnis ", unternimmt dann aber zusätzliche Anstrengungen, um zu zeigen, wie Befehle ungültig werden.
Geben Befehlshandler Werte zurück oder nicht?
Die Antwort?
Ausgehend von Jimmy Bogards " CQRS Myths " denke ich, dass die Antwort (en) auf diese Frage davon abhängt, von welchem programmatischen / kontextuellen "Quadranten" Sie sprechen:
+-------------+-------------------------+-----------------+
| | Real-time, Synchronous | Queued, Async |
+-------------+-------------------------+-----------------+
| Acceptance | Exception/return-value* | <see below> |
| Fulfillment | return-value | n/a |
+-------------+-------------------------+-----------------+
Akzeptanz (zB Validierung)
Der Befehl "Akzeptanz" bezieht sich hauptsächlich auf die Validierung. Vermutlich müssen Validierungsergebnisse synchron an den Anrufer weitergegeben werden, unabhängig davon, ob der Befehl "Erfüllung" synchron ist oder sich in der Warteschlange befindet.
Es scheint jedoch, dass viele Praktiker die Validierung nicht über den Befehlshandler initiieren. Soweit ich gesehen habe, liegt dies entweder daran, dass (1) sie bereits eine fantastische Möglichkeit gefunden haben, die Validierung auf Anwendungsebene durchzuführen (dh ein ASP.NET MVC-Controller, der den gültigen Status über Datenanmerkungen überprüft) oder (2) an einer Architektur ist vorhanden, bei dem davon ausgegangen wird, dass Befehle an einen Bus oder eine Warteschlange (außerhalb des Prozesses) gesendet werden. Diese letzteren Formen der Asynchronität bieten im Allgemeinen keine synchrone Validierungssemantik oder -schnittstellen.
Kurz gesagt, viele Designer möchten möglicherweise, dass der Befehlshandler Validierungsergebnisse als (synchronen) Rückgabewert bereitstellt, müssen jedoch mit den Einschränkungen der von ihnen verwendeten Asynchronisierungstools leben.
Erfüllung
In Bezug auf die "Erfüllung" eines Befehls muss der Client, der den Befehl ausgegeben hat, möglicherweise die scope_identity für einen neu erstellten Datensatz oder möglicherweise Fehlerinformationen kennen, z. B. "Konto überzogen".
In einer Echtzeiteinstellung scheint ein Rückgabewert am sinnvollsten zu sein. Ausnahmen sollten nicht verwendet werden, um geschäftsbezogene Fehlerergebnisse zu kommunizieren. In einem "Warteschlangen" -Kontext ... geben Rückgabewerte natürlich keinen Sinn.
Hier lässt sich vielleicht die ganze Verwirrung zusammenfassen:
Viele (die meisten?) CQRS-Anwender gehen davon aus, dass sie jetzt oder in Zukunft ein Asynchronitätsframework oder eine Asynchronitätsplattform (einen Bus oder eine Warteschlange) integrieren werden, und proklamieren daher, dass Befehlshandler keine Rückgabewerte haben. Einige Praktiker haben jedoch nicht die Absicht, solche ereignisgesteuerten Konstrukte zu verwenden, und unterstützen daher Befehlshandler, die (synchron) Werte zurückgeben.
Ich glaube zum Beispiel, dass ein synchroner Kontext (Anfrage-Antwort) angenommen wurde, als Jimmy Bogard diese Beispielbefehlsschnittstelle bereitstellte :
public interface ICommand<out TResult> { }
public interface ICommandHandler<in TCommand, out TResult>
where TCommand : ICommand<TResult>
{
TResult Handle(TCommand command);
}
Sein Mediatr-Produkt ist schließlich ein In-Memory-Tool. Angesichts all dessen denke ich, dass Jimmy sich die Zeit sorgfältig genommen hat, um eine ungültige Rückgabe von einem Befehl zu erzeugen, nicht, weil "Befehlshandler keine Rückgabewerte haben sollten", sondern weil er einfach wollte, dass seine Mediator-Klasse eine konsistente Schnittstelle hat:
public interface IMediator
{
TResponse Request<TResponse>(IQuery<TResponse> query);
TResult Send<TResult>(ICommand<TResult> query); //This is the signature in question.
}
... obwohl nicht alle Befehle einen aussagekräftigen Wert für die Rückgabe haben.
Wiederholen und abschließen
Erfasse ich richtig, warum es zu diesem Thema Verwirrung gibt? Fehlt mir etwas?
Update (6/2020)
Mit Hilfe der gegebenen Antworten denke ich, dass ich die Verwirrung entwirrt habe. Einfach ausgedrückt, wenn ein CQRS-Befehl einen Erfolg / Misserfolg zurückgeben kann, der den Abschlussstatus anzeigt , ist ein Rückgabewert sinnvoll. Dies umfasst die Rückgabe einer neuen DB-Zeilenidentität oder eines Ergebnisses, das den Inhalt des Domänenmodells (Geschäfts) nicht liest oder zurückgibt.
Ich denke, wo "CQRS-Befehl" Verwirrung auftaucht, ist über die Definition und Rolle von "Asynchronität". Es gibt einen großen Unterschied zwischen "aufgabenbasierten" asynchronen E / A-Vorgängen und einer asynchronen Architektur (z. B. warteschlangenbasierte Middleware). Im ersteren Fall kann und wird die asynchrone "Task" das Abschlussergebnis für den asynchronen Befehl liefern. Ein an RabbitMQ gesendeter Befehl erhält jedoch ebenfalls keine Benachrichtigung über den Abschluss der Anforderung / Antwort. Es ist dieser letztere Kontext der Async-Architektur, der einige dazu bringt zu sagen: "Es gibt keinen asynchronen Befehl" oder "Befehle geben keine Werte zurück".
quelle
Antworten:
Im Anschluss an die Beratung in Angreifen Komplexität in CQRS von Vladik Khononov schlägt Befehl Handhabung Informationen in Bezug auf ihr Ergebnis zurückgeben kann.
Daniel Whittaker befürwortet die Rückgabe eines Objekts mit " gemeinsamem Ergebnis " von einem Befehlshandler, der diese Informationen enthält.
quelle
Sie sollten keine Geschäftsdaten zurückgeben , sondern nur Metadaten (in Bezug auf den Erfolg oder Misserfolg der Ausführung des Befehls).
CQRS
ist CQS auf ein höheres Niveau gebracht. Selbst wenn Sie die Regeln des Puristen brechen und etwas zurückgeben würden, was würden Sie zurückgeben? In CQRS ist der Befehlshandler eine Methode vonapplication service
, die lädt undaggregate
dann eine Methode aufruft, dieaggregate
dann beibehalten wirdaggregate
. Der Befehlshandler beabsichtigt, das zu ändernaggregate
. Sie würden nicht wissen, was Sie zurückgeben sollen, das wäre unabhängig vom Anrufer. Jeder Befehlshandler-Aufrufer / Client möchte etwas anderes über den neuen Status wissen.Wenn die Befehlsausführung blockiert (auch bekannt als synchron), müssen Sie nur wissen, ob der Befehl erfolgreich ausgeführt wurde oder nicht. In einer höheren Ebene würden Sie dann genau das abfragen, was Sie über den Status der neuen Anwendung wissen müssen, indem Sie ein Abfragemodell verwenden, das Ihren Anforderungen am besten entspricht.
Wenn Sie etwas von einem Befehlshandler zurückgeben, geben Sie ihm zwei Verantwortlichkeiten: 1. Ändern Sie den Aggregatstatus und 2. fragen Sie ein Lesemodell ab.
In Bezug auf die Befehlsüberprüfung gibt es mindestens zwei Arten der Befehlsüberprüfung:
Wenn wir jedoch eine Stufe höher gehen, in dem
Presentation layer
(dh einemREST
Endpunkt), dem Client vonApplication layer
, könnten wir alles zurückgeben und wir werden die Regeln nicht brechen, da die Endpunkte nach den Anwendungsfällen entworfen wurden, wissen Sie genau, was Sie wollen Rückkehr, nachdem ein Befehl ausgeführt wurde, in jedem Anwendungsfall.quelle
CQRS und CQS sind wie Mikrodienste und Klassenzerlegung: Die Hauptidee ist dieselbe ("neigen zu kleinen zusammenhängenden Modulen"), aber sie liegen auf verschiedenen semantischen Ebenen.
Der Zweck von CQRS besteht darin, Schreib- / Lesemodelle zu trennen. Solche Details auf niedriger Ebene wie der Rückgabewert einer bestimmten Methode sind völlig irrelevant.
Beachten Sie das folgende Zitat von Fowler :
Es geht um Modelle , nicht um Methoden .
Der Befehlshandler kann alles außer Lesemodellen zurückgeben: Status (Erfolg / Misserfolg), generierte Ereignisse (das Hauptziel der Befehlshandler, übrigens: Ereignisse für den angegebenen Befehl zu generieren), Fehler. Befehlshandler lösen sehr oft eine ungeprüfte Ausnahme aus. Dies ist ein Beispiel für Ausgangssignale von Befehlshandlern.
Darüber hinaus sagt der Autor des Begriffs, Greg Young, dass Befehle immer synchron sind (andernfalls werden sie zu Ereignissen): https://groups.google.com/forum/#!topic/dddcqrs/xhJHVxDx2pM
quelle
Antwort für @Constantin Galbenu, ich stand vor Limit.
@Constantin Galbenu, in den meisten Fällen brauche ich sie natürlich nicht als Ergebnis des Befehls. In einigen Fällen muss ich den Client als Antwort auf diese API-Anfrage benachrichtigen.
Es ist äußerst nützlich, wenn:
Und ich kann ein Beispiel für den zweiten Fall geben. Stellen Sie sich vor, wir machen einen Zunder-ähnlichen Dienst, wir haben den LikeStranger-Befehl. Dieser Befehl kann zu StrangersWereMatched führen, wenn wir eine Person mögen, die uns bereits zuvor gemocht hat. Wir müssen den mobilen Client darüber informieren, ob eine Übereinstimmung stattgefunden hat oder nicht. Wenn Sie nach dem Befehl nur matchQueryService überprüfen möchten, finden Sie dort möglicherweise eine Übereinstimmung, aber es gibt keine Garantie dafür, dass die Übereinstimmung gerade stattgefunden hat, da SOMETIMES Tinder bereits übereinstimmende Fremde anzeigt (wahrscheinlich in unbewohnten Bereichen, möglicherweise inkonsistent, wahrscheinlich nur 2. Gerät usw.).
Das Überprüfen der Antwort, ob StrangersWereMatched gerade wirklich passiert ist, ist so einfach:
Ja, Sie können beispielsweise die Befehls-ID einführen und das Match-Lesemodell so einstellen, dass es erhalten bleibt:
... aber denken Sie darüber nach: Warum ist das erste Beispiel mit einfacher Logik Ihrer Meinung nach schlechter? Es verstößt sowieso nicht gegen CQRS, ich habe nur das Implizite explizit gemacht. Es ist ein staatenloser unveränderlicher Ansatz. Weniger Chancen, einen Fehler zu finden (z. B. wenn
matchQueryService
zwischengespeichert / verzögert wird [nicht sofort konsistent], haben Sie ein Problem).Ja, wenn die Tatsache des Abgleichs nicht ausreicht und Sie Daten für die Antwort abrufen müssen, müssen Sie den Abfragedienst verwenden. Nichts hindert Sie jedoch daran, Ereignisse vom Befehlshandler zu empfangen.
quelle