Warum trennen Sie die Klasse CommandHandler mit Handle (), anstatt die Methode in Command selbst zu behandeln?

13

Ich habe einen Teil des CQRS-Musters mit der folgenden S # arp-Architektur implementiert :

public class MyCommand
{
    public CustomerId { get; set; }

    // some other fields
}

public class MyCommandHandler<MyCommand> : ICommandHandler<MyCommand, CommandResult>
{
    Handle(MyCommand command)
    {
        // some code for saving Customer entity

        return CommandResult.Success;
    }
}

Ich frage mich, warum es nicht einfach eine Klasse gibt, Commanddie sowohl Daten als auch Handhabungsmethoden enthält. Ist dies eine Art Testbarkeitsvorteil, bei dem Sie die Befehlsverarbeitungslogik getrennt von den Befehlseigenschaften testen müssen? Oder handelt es sich um eine häufige Geschäftsanforderung, bei der ein Befehl von verschiedenen Implementierungen von verarbeitet werden muss ICommandHandler<MyCommand, CommandResult>?

Greifer
quelle
Ich hatte die gleiche Frage, die einen Blick wert war: blogs.cuttingedge.it/steven/posts/2011/…
rdhaundiyal

Antworten:

14

Witzig, diese Frage erinnerte mich nur an genau dasselbe Gespräch, das ich mit einem unserer Ingenieure über die Kommunikationsbibliothek geführt hatte, an der ich arbeitete.

Anstelle von Befehlen hatte ich Request-Klassen und dann RequestHandler. Das Design war sehr ähnlich zu dem, was Sie beschreiben. Ich denke, ein Teil der Verwirrung, die Sie haben, ist, dass Sie das englische Wort "command" sehen und sofort "verb, action ... etc" denken.

Stellen Sie sich Command (oder Request) in diesem Entwurf jedoch als Buchstaben vor. Oder für diejenigen, die nicht wissen, was ein Postdienst ist, denken Sie an E-Mail. Es ist einfach Inhalt, entkoppelt davon, wie auf diesen Inhalt reagiert werden soll.

Wieso würdest du das machen? In den meisten einfachen Fällen gibt es für Command Pattern keinen Grund, und Sie könnten diese Klasse direkt arbeiten lassen. Die Entkopplung wie in Ihrem Entwurf ist jedoch sinnvoll, wenn Ihre Aktion / Ihr Befehl / Ihre Anforderung eine gewisse Strecke zurücklegen muss. Zum Beispiel über Sockets oder Pipes oder zwischen Domäne und Infrastruktur. Oder vielleicht müssen Ihre Befehle in Ihrer Architektur persistent sein (z. B. kann der Befehlshandler aufgrund einiger Systemereignisse jeweils 1 Befehl ausführen, 200 Befehle treffen ein und nach dem Herunterfahren der ersten 40 Prozesse). In diesem Fall ist es mit einer einfachen Nur-Nachrichten-Klasse sehr einfach, nur den Nachrichtenteil in JSON / XML / binary / whatever zu serialisieren und über die Pipeline weiterzuleiten, bis der Befehlshandler bereit ist, ihn zu verarbeiten.

Ein weiterer Vorteil der Entkopplung von Command von CommandHandler besteht darin, dass Sie jetzt die Möglichkeit haben, eine parallele Vererbungshierarchie zu erstellen. Beispielsweise könnten alle Ihre Befehle von einer Basisbefehlsklasse abgeleitet werden, die die Serialisierung unterstützt. Und vielleicht haben Sie 4 von 20 Befehlshandlern, die eine große Ähnlichkeit aufweisen, jetzt können Sie die von der Came-Handler-Basisklasse ableiten. Wenn Sie Daten- und Befehlsverarbeitung in einer Klasse haben würden, würde diese Art von Beziehung schnell außer Kontrolle geraten.

Ein weiteres Beispiel für die Entkopplung wäre, wenn Ihr Befehl nur sehr wenige Eingaben erfordert (z. B. 2 Ganzzahlen und eine Zeichenfolge), die Verarbeitungslogik jedoch so komplex ist, dass Sie Daten in den Variablen der Zwischenelemente speichern möchten. Wenn Sie 50 Befehle in die Warteschlange stellen, möchten Sie nicht den gesamten Zwischenspeicher belegen. Trennen Sie daher Command von CommandHandler. Jetzt stellen Sie 50 leichte Datenstrukturen in die Warteschlange, und komplexerer Datenspeicher wird vom CommandHandler, der die Befehle verarbeitet, nur einmal (oder N-mal, wenn Sie N-Handler haben) zugewiesen.

DXM
quelle
Der Punkt ist, dass in diesem Kontext der Befehl / die Anforderung nicht remote / persistent / usw. ist. Er wird direkt behandelt. Und ich kann nicht sehen, wie die Trennung der beiden bei der Vererbung helfen würde. Es würde es tatsächlich schwieriger machen. Der letzte Absatz ist auch eine Art Fräulein. Objekterstellung ist keine teure Operation und 50 Befehle sind vernachlässigbare Zahlen.
Euphoric
@Euphoric: Woher weißt du, was der Kontext ist? Sofern S # arp Architecture nicht etwas Besonderes ist, sehe ich nur ein paar Klassendeklarationen und Sie haben keine Ahnung, wie sie im Rest der Anwendung verwendet werden. Wenn Ihnen die Zahlen, die ich gewählt habe, nicht gefallen, wählen Sie 50 pro Sekunde. Wenn das nicht ausreicht, wählen Sie 1000 pro Sekunde. Ich habe nur versucht, Beispiele zu nennen. Oder denkst du nicht, dass er in diesem Zusammenhang so viele Befehle haben wird?
DXM
Die genaue Struktur finden Sie beispielsweise hier: weblogs.asp.net/shijuvarghese/archive/2011/10/18/… . Und nirgendwo steht, was du gesagt hast. Was die Geschwindigkeit betrifft, ist das Problem, dass Sie das Argument "Leistung" ohne Profilerstellung verwendet haben. Wenn Sie Anforderungen für einen solchen Durchsatz haben, werden Sie keine generische Architektur verwenden, sondern etwas spezialisierteres erstellen.
Euphoric
1
Lassen Sie mich sehen, ob dies Ihr letzter Punkt war: OP hat nach Beispielen gefragt, und ich hätte sagen sollen: Zuerst entwerfen Sie die einfache Art und Weise und Ihre Anwendung funktioniert, dann skalieren Sie und erweitern die Stellen, an denen Sie das Befehlsmuster verwenden Wenn Sie in Betrieb gehen und 10.000 Computer mit Ihrem Server kommunizieren und Ihr Server weiterhin Ihre ursprüngliche Architektur verwendet, profilieren Sie das Problem und identifizieren es. Anschließend können Sie die Befehlsdaten von der Befehlsverarbeitung trennen, jedoch erst, nachdem Sie ein Profil erstellt haben. Würde es Sie wirklich glücklicher machen, wenn ich das alles in die Antwort einbeziehe? Er bat um ein Beispiel, ich gab ihm eines.
DXM
... also habe ich nur einen Blick in den Blogpost geworfen, den Sie gepostet haben, und der scheint mit dem übereinzustimmen, was ich geschrieben habe: Trennen Sie sie, wenn Ihr Befehl eine gewisse Strecke zurücklegen muss. In dem Blog scheint er sich auf einen Befehlsbus zu beziehen, der im Grunde nur eine andere Pipe, ein Socket, eine Nachrichtenwarteschlange, ein Esb ... usw. ist
DXM
2

Bei einem normalen Befehlsmuster geht es darum, Daten und Verhalten in einer Klasse zu haben. Diese Art von 'Command / Handler-Muster' unterscheidet sich geringfügig. Der einzige Vorteil gegenüber dem normalen Muster ist der zusätzliche Vorteil, dass Ihr Befehl nicht von Frameworks abhängt. Beispielsweise benötigt Ihr Befehl möglicherweise DB-Zugriff, sodass er über einen DB-Kontext oder eine DB-Sitzung verfügen muss, was bedeutet, dass er von Frameworks abhängig ist. Dieser Befehl kann jedoch Teil Ihrer Domain sein, sodass Sie nicht möchten, dass er von den Frameworks gemäß abhängt Dependency Inversion Principle entsprechen . Dies kann behoben werden, indem die Eingabe- und Ausgabeparameter vom Verhalten getrennt werden und ein Dispatcher sie verkabelt.

Auf der anderen Seite verlieren Sie den Vorteil sowohl der Vererbung als auch der Zusammensetzung von Befehlen. Was ich denke, ist es wahre Macht.

Auch kleiner Nitpick. Nur weil es Command im Namen hat, ist es nicht Teil von CQRS. Das ist etwas viel grundlegenderes. Diese Art von Struktur kann gleichzeitig sowohl als Befehl als auch als Abfrage dienen.

Euphorisch
quelle
Ich habe den Link weblogs.asp.net/shijuvarghese/archive/2011/10/18/… gesehen, auf den Sie hingewiesen haben, aber ich sehe keine Anzeichen eines Busses in meinem S # arp Arch-Code. Also, ich denke, eine solche Trennung in meinem Fall verbreitet nur Klassen und spritzt Logik.
Rgripper
Hmm, danke für den Hinweis. Dann ist mein Fall noch ein bisschen schlimmer, weil in dem Code, den ich habe, ICommandProcessor IOC'ed und zu CommandProcessor aufgelöst ist (der selbst ein IOC für die Befehlshandler macht) - eine schlammige Komposition. Und in dem Projekt scheint es keine Geschäftsfälle für mehr als einen Hadler für einen Befehl zu geben.
Rgripper