Warum werden Befehle und Ereignisse separat dargestellt?


77

Was ist der Unterschied zwischen Befehlen und Ereignissen in Architekturen, die Ereignisse hervorheben? Der einzige Unterschied, den ich sehen kann, ist, dass Befehle normalerweise von Akteuren außerhalb des Systems bezogen / aufgerufen werden, während Ereignisse von Handlern und anderem Code in einem System bezogen zu werden scheinen. In vielen Beispielanwendungen, die ich gesehen habe, haben sie jedoch unterschiedliche (aber funktional ähnliche) Schnittstellen.


Es hängt stark davon ab, was Sie genau im Sinn haben, wenn Sie die Wörter "Befehl" und "Ereignis" verwenden.
Doc Brown

Nehmen Sie als Beispiel Ihr typisches CQRS / DDD-Projekt.
Alphadogg

4
Mein Verständnis: Befehle müssen von genau einem Empfänger verarbeitet werden (der den Befehl ablehnen kann), und Ereignisse können von 0 ... n Empfängern verarbeitet werden.
Levi Fuller

Antworten:


155

Befehle können abgelehnt werden.

Ereignisse sind passiert.

Dies ist wahrscheinlich der wichtigste Grund. In einer ereignisgesteuerten Architektur kann es keine Frage geben, dass ein ausgelöstes Ereignis etwas darstellt, das passiert ist .

Nun, da Befehle sind etwas , was wir wollen passieren, und Ereignisse sind etwas , das ist passiert, sollten wir verschiedene Verben verwenden , wenn wir diese Dinge nennen. Dies führt zu separaten Darstellungen.

Ich kann sehen, dass Befehle normalerweise von Akteuren außerhalb des Systems bezogen / aufgerufen werden, während Ereignisse von Handlern und anderem Code in einem System bezogen zu werden scheinen

Dies ist ein weiterer Grund, warum sie separat dargestellt werden. Konzeptionelle Klarheit.

Befehle und Ereignisse sind beide Nachrichten. Tatsächlich handelt es sich jedoch um separate Konzepte, und Konzepte sollten explizit modelliert werden.


Okay, gibt es praktische Unterschiede auf Implementierungsebene zwischen ihnen? Zum Beispiel eine andere Schnittstelle?
Alphadogg

9
Ja, würde ich vor allem beim Versand sagen. Befehle werden an einen einzelnen Handler gesendet, Ereignisse werden jedoch an mehrere Listener gesendet. Zugegeben, die Implementierungsunterschiede liegen in den Bussen, aber ich verwende immer noch separate Ereignis- und Befehlsschnittstellen, sodass jeder Bus nur die Nachrichten entgegennimmt, die er kann.
Quentin-Starin

8
Bei Befehlen verwenden Sie das Wort "Senden" (Sie interessieren sich für das Ziel dieser Operation), während Sie bei Ereignissen "Veröffentlichen" verwenden (es ist Ihnen egal, wer am anderen Ende ist).
Yves Reynhout

4
@ quentin-starin Ich weiß, dass dies alt ist, aber ich wünschte, ich könnte Ihre Antwort etwa zehnmal positiv bewerten ... Ich wollte den Weg gehen: "Warum ist nicht alles ein Ereignis, einige von ihnen werden einfach bearbeitet?" - zB "SomethingRequested" und dann "SomethingHappened"
Michael Wasser

1
Stellen Sie sich eine Entität vor, die in MySQL beibehalten wird und das Ereignis entityIndexed auslöst. Das gleiche Ereignis wird von einem Indexdienst abgehört, der beim Empfang des Ereignisses die Entität abruft und sie in elasticsearch indiziert. Würden Sie es aus Sicht des Indexdienstes immer noch als Ereignis oder Befehl bezeichnen?
Technoshaft

7

Zusätzlich zu allen hier offengelegten Antworten kann ein Ereignishandler möglicherweise auch einen Befehl auslösen, nachdem er eine Benachrichtigung über das Auftreten eines Ereignisses erhalten hat.

Angenommen, Sie möchten nach dem Erstellen eines Kunden auch einige Kontenwerte usw. initialisieren. Nachdem Ihr Kunden-AR das Ereignis zum EventDispatcher hinzugefügt hat und dieses von einem CustomerCreatedEventHandler-Objekt empfangen wird, kann dieser Handler einen Versand eines Befehls auslösen, der führt alles aus, was Sie brauchen, etc.

Es gibt auch DomainEvents und ApplicationEvents. Der Unterschied ist einfach konzeptionell. Sie möchten zuerst alle Ihre Domänenereignisse auslösen (einige von ihnen können Anwendungsereignisse erzeugen). Was meine ich damit?

Das Initialisieren eines Kontos nach dem Auftreten eines CustomerCreatedEvent ist ein DOMAIN-Ereignis. Das Senden einer E-Mail-Benachrichtigung an den Kunden ist ein Anwendungsereignis.

Der Grund, warum Sie sie nicht mischen sollten, ist klar. Wenn Ihr SMTP-Server vorübergehend außer Betrieb ist, bedeutet dies nicht, dass Ihr DOMAIN-BETRIEB davon betroffen sein sollte. Sie möchten weiterhin einen nicht beschädigten Status Ihrer Aggregate beibehalten.

Normalerweise füge ich meinem Dispatcher Ereignisse auf der Ebene des aggregierten Stamms hinzu. Diese Ereignisse sind entweder DomainEvents oder ApplicationEvents. Kann beides sein und kann viele von ihnen sein. Sobald mein Befehlshandler fertig ist und ich wieder im Stapel zu dem Code bin, der den Befehlshandler ausführt, überprüfe ich meinen Dispatcher und versende jedes andere DomainEvent. Wenn all dies erfolgreich ist, schließe ich die Transaktion ab.

Wenn ich Anwendungsereignisse habe, ist dies die Zeit, diese zu versenden. Das Senden einer E-Mail erfordert nicht unbedingt eine offene Verbindung zu einer Datenbank oder einen offenen Transaktionsbereich.

Ich bin ein wenig von der ursprünglichen Frage abgewichen, aber es ist auch wichtig, dass Sie verstehen, wie Ereignisse auch konzeptionell anders behandelt werden können.

Dann haben Sie Sagas ... aber das ist WAYYYY OFF vom Umfang dieser Frage :)

Macht das Sinn?


Sie können jedoch genauso gut argumentieren, dass Sie Domänenereignisse, Anwendungsereignisse und Benutzerereignisse haben. Dann geht es nur noch darum, woher das Ereignis stammt. Ich verstehe, wie nützlich die Unterscheidung sein kann, aber vielleicht mehr, um zwischen einer Anfrage und einer daraus resultierenden Antwort / Aktion zu unterscheiden. Aber auch dort bin ich noch nicht ganz überzeugt. Müssen mehr Nachforschungen anstellen.
Arwin

Nun, da ich meine eigene Antwort gelesen habe, kann ich die Verwirrung sehen. Die Ereignisse können tatsächlich eins sein. Veranstaltungen. Dann haben Sie die Handler, die diese Unterscheidung haben können. Möglicherweise haben Sie zwei Handler für dasselbe Ereignis, die Sie dann in verschiedenen Kontexten auslösen können. Sie können entscheiden, "Domain Event Handler" innerhalb der aktiven Transaktion auszulösen, und anschließend können Sie Ihre "Application Event Handler" auslösen. Ich sehe einfach keinen Sinn darin, eine E-Mail oder eine SMS innerhalb einer Transaktion zu senden. Vielleicht können Sie einer Datenbank eine "Aufgabe" hinzufügen und von einem anderen Prozess ausführen lassen, z. B. das Senden von E-Mails. Macht das Sinn?
Pepito Fernandez

6

Die Veranstaltung ist eine Tatsache aus der Vergangenheit.

Der Befehl ist nur eine Anfrage und kann daher abgelehnt werden.

Ein wichtiges Merkmal eines Befehls ist, dass er nur einmal von einem einzelnen Empfänger verarbeitet werden sollte. Dies liegt daran, dass ein Befehl eine einzelne Aktion oder Transaktion ist, die Sie in der Anwendung ausführen möchten. Beispielsweise sollte derselbe Befehl zur Auftragserstellung nicht mehr als einmal verarbeitet werden. Dies ist ein wichtiger Unterschied zwischen Befehlen und Ereignissen. Ereignisse können mehrfach verarbeitet werden, da möglicherweise viele Systeme oder Mikrodienste an dem Ereignis interessiert sind. 'msdn'


5

Nachdem ich einige Beispiele und insbesondere die Präsentation von Greg Young ( http://www.youtube.com/watch?v=JHGkaShoyNs ) durchgearbeitet habe, bin ich zu dem Schluss gekommen, dass Befehle redundant sind. Es sind einfach Ereignisse von Ihrem Benutzer, sie haben diese Taste gedrückt. Sie sollten diese genauso speichern wie andere Ereignisse, da es sich um Daten handelt und Sie nicht wissen, ob Sie sie in einer zukünftigen Ansicht verwenden möchten. Ihr Benutzer hat diesen Artikel hinzugefügt und später aus dem Warenkorb entfernt oder zumindest versucht. Möglicherweise möchten Sie diese Informationen später verwenden, um den Benutzer zu einem späteren Zeitpunkt daran zu erinnern.


In der von @ quentin-starin beschriebenen Weise wird die Aufzeichnung von Ereignissen durch Drücken von Tasten nicht gestoppt, wenn das Ereignis als etwas betrachtet wird, das geschehen ist, und ein Befehl als etwas, das geschehen soll (eine Anfrage). t führt notwendigerweise zu einem Befehl oder zu einem Befehl, auf den reagiert wurde.
Fraktor

Ich bin immer noch der Meinung, dass Befehle überflüssig sind. Ich nenne einfach das, was ich mache, Sourcing funktionaler Ereignisse. Ein aktueller Blog von mir mit ES und F # Elm als Komplettsystem: anthonylloyd.github.io/blog/2016/11/27/event-sourcing
Ant

2
Befehle entkoppeln lokale Ereignisse von Remote-Aktionen. In Ihrem Beispiel reagiert der Benutzer des UserPressedButton-Ereignisses nicht auf ein UserSelectedMenu oder ScriptDidSomething, es sei denn, er weiß auch über diese Dinge Bescheid. Darüber hinaus richten sich Befehle im Allgemeinen an einen bestimmten Verbraucher. Auch in Ihrem Beispiel kann der Benutzer des UserPressedButton-Ereignisses nicht feststellen, ob der Benutzer das Kontrollkästchen "Bestätigen" aktiviert hat oder nicht, es sei denn, wir fügen noch mehr Kopplung hinzu. Bei Befehlen kann die durchgeführte Aktion vom Status des Absenders oder sogar von einer externen Richtlinie abhängen. Ereignisse allein machen dies nahezu unmöglich.
Doktor Eval

Schauen Sie sich dieses Projekt an - github.com/gregoryyoung/mr . Es werden sowohl Befehle als auch Ereignisse verwendet.
Xhafan

6
Es ist einfacher, Befehle von Ereignissen zu unterscheiden, wenn man an die strengste Implementierung denkt: Event Sourcing. Hier sind die Ereignisse die einzige Quelle der Wahrheit. Sie können jederzeit Ihren vollständigen Status erstellen, indem Sie nur die Ereignisse wiedergeben. Die Befehle im Gegensatz sind Anforderungen , die möglicherweise in Ereignissen führen, aber auch abgelehnt bekommen könnte. Für die Wiederherstellung sind die Statusbefehle unwichtig. Wenn Ihr System einen Befehl nicht verarbeitet (z. B. aufgrund von Validierungsfehlern), ist dies in Ordnung. Wenn Ihr System ein Ereignis nicht verarbeitet, wird Ihr Status beschädigt.
Sven

2

Sie werden getrennt dargestellt, weil sie sehr unterschiedliche Dinge darstellen. Wie @qstarin sagte, sind Befehle Nachrichten, die abgelehnt werden können und die bei Erfolg ein Ereignis erzeugen. Befehle und Ereignisse sind Dtos, sie sind Nachrichten und sie sehen beim Erstellen und Entifizieren sehr ähnlich aus, jedoch von da an nicht unbedingt.

Wenn Sie sich Sorgen über die Wiederverwendung machen, können Sie Befehle und Ereignisse als Umschläge für Ihre (Messge-) Nutzdaten verwenden

class CreateSomethingCommand
{
    public int CommandId {get; set;}

    public SomethingEnvelope {get; set;}
 }

Ich würde jedoch gerne wissen, warum Sie fragen: Haben Sie zu viele Befehle / Ereignisse?


1
Nein, ich habe nicht zu viele, da ich mein erstes solches System bauen möchte! :) Ich bin im Lernmodus. Ich versuche zu verstehen, ob CommandHandlers und EventHandlers etwas anderes tun oder im Grunde die gleiche Schnittstelle haben.
Alphadogg

Ein interessanter Punkt ist, dass Befehle und Ereignisse unterschiedlich sein können. Angenommen, Sie haben einen CheckoutCartCommand. Das Ereignis enthält wahrscheinlich viel mehr Daten als der Befehl. Möglicherweise gibt es auch viele Befehle. Ich würde Ihnen wärmstens empfehlen, einen Blick auf github.com/MarkNijhof/Fohjin und github.com/gregoryyoung/mr
Roundcrisis

In Bezug auf Ihr Beispiel befinden sich Umschläge normalerweise außen und nicht innen (z. B. ein Seifenumschlag). Und ich denke, es fehlt ein Eigenschaftsname (Payload?).
Yves Reynhout

@Yves: Nun, da das Something Evelope Informationen enthält, die für den Befehl wesentlich sind (wenn dies eine Kundenerstellung wäre, denke ich an E-Mail), würde ich das ziemlich seltsam finden, findest du nicht?
Roundcrisis

Ich glaube nicht, dass Sie das Konzept eines Umschlags verstehen. Wenn Sie bestimmte Dinge für den Befehl haben, fügen Sie diese entweder als Nutzlast oder als Header (Out-of-Band) in den Befehl ein. Nennen Sie die Nutzdaten / Header jedoch keinen Umschlag.
Yves Reynhout

2

Zusätzlich zu den oben erwähnten konzeptionellen Unterschieden gibt es meines Erachtens noch einen weiteren Unterschied in Bezug auf gemeinsame Implementierungen:

Ereignisse werden normalerweise in einer Hintergrundschleife verarbeitet, die die Ereigniswarteschlangen abfragen muss . Jede Partei, die daran interessiert ist, auf das Ereignis zu reagieren, kann normalerweise einen Rückruf registrieren, der als Ergebnis der Verarbeitung der Ereigniswarteschlange aufgerufen wird. Ein Ereignis kann also eins zu viele sein .

Befehle müssen möglicherweise nicht auf diese Weise verarbeitet werden. Der Urheber des Befehls hat normalerweise Zugriff auf den beabsichtigten Ausführenden des Befehls. Dies kann beispielsweise in Form einer Nachrichtenwarteschlange an den Executor erfolgen. Somit ist ein Befehl für eine einzelne Entität vorgesehen .


1

Ich denke, etwas, das Quentin-Santins Antwort hinzufügen kann, ist, dass sie:

Kapseln Sie eine Anforderung als Objekt, sodass Sie Clients mit unterschiedlichen Anforderungen, Warteschlangen- oder Protokollanforderungen parametrisieren und rückgängig zu machende Vorgänge unterstützen können.

Quelle .


0

Sie können einen Status nicht basierend auf Befehlen neu berechnen, da diese im Allgemeinen bei jeder Verarbeitung unterschiedliche Ergebnisse liefern können.

Stellen Sie sich zum Beispiel einen GenerateRandomNumberBefehl vor. Jedes Mal, wenn es aufgerufen wird, wird eine andere Zufallszahl X erzeugt. Wenn Ihr Status von dieser Zahl abhängt, erhalten Sie jedes Mal, wenn Sie Ihren Status aus dem Befehlsverlauf neu berechnen, einen anderen Status.

Ereignisse lösen dieses Problem. Wenn Sie einen Befehl ausführen, wird eine Folge von Ereignissen erzeugt, die das Ergebnis der Befehlsausführung darstellen. Beispielsweise GenerateRandomNumberkönnte der Befehl ein GeneratedNumber(X)Ereignis erzeugen , das die generierte Zufallszahl protokolliert. Wenn Sie jetzt Ihren Status aus dem Ereignisprotokoll neu berechnen, erhalten Sie immer denselben Status, da Sie immer dieselbe Nummer verwenden, die durch eine bestimmte Ausführung des Befehls generiert wurde.

Mit anderen Worten, Befehle sind Funktionen mit Nebenwirkungen. Ereignisse zeichnen das Ergebnis einer bestimmten Ausführung eines Befehls auf.

Hinweis: Sie können weiterhin einen Verlauf von Befehlen für Prüf- oder Debugging-Zwecke aufzeichnen. Der Punkt ist, dass Sie zur Neuberechnung des Status den Verlauf von Ereignissen verwenden, nicht den Verlauf von Befehlen.


0

Nur um diese großartigen Antworten zu ergänzen. Ich möchte auf Unterschiede in Bezug auf die Kopplung hinweisen.

Befehle sind an einen bestimmten Prozessor gerichtet. Somit besteht ein gewisses Maß an Abhängigkeit / Kopplung mit dem Befehlsinitiator und dem Prozessor.

Beispielsweise UserServicesendet a beim Erstellen eines neuen Benutzers einen Befehl "E-Mail senden" an EmailService.

Die Tatsache, dass das UserServiceweiß, dass es das braucht EmailService, das koppelt schon. Wenn EmailServicedas API-Schema geändert wird oder ausfällt, wirkt sich dies direkt auf die UserServiceFunktion aus.


Ereignisse richten sich nicht an einen bestimmten Ereignishandler. Dadurch wird der Event-Publisher lose gekoppelt. Es ist egal, welcher Dienst seine Veranstaltung verbraucht. Es ist sogar gültig, 0 Verbraucher eines Ereignisses zu haben.

Beispielsweise veröffentlicht a UserServicebeim Erstellen eines neuen Benutzers ein "Benutzer erstelltes Ereignis". Möglicherweise EmailServicekann ein Benutzer dieses Ereignis nutzen und eine E-Mail an den Benutzer senden.

Hier UserServiceist sich das nicht bewusst EmailService. Sie sind völlig entkoppelt. Wenn das EmailServiceausfällt oder Geschäftsregeln geändert werden, müssen wir nur das bearbeitenEmailService


Beide Ansätze haben Vorzüge. Ein rein ereignisgesteuertes Architekturdesign ist schwieriger zu verfolgen, da es insbesondere bei großen Systemen zu locker gekoppelt ist. Und eine befehlslastige Architektur weist ein hohes Maß an Kopplung auf. Eine gute Balance ist also ideal.

Hoffe das macht Sinn.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.