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