This is an old revision of the document!
Wikipedia sagt
Idempotenz ist ein Begriff aus der Mathematik und Informatik. Man bezeichnet ein Element einer Menge (also auch eine Funktion), das mit sich selbst verknüpft wieder sich selbst ergibt, als idempotent.
Der Befehl “setze den Kontostand auf € 1.000,-” ist idempotent, weil die mehrfache Ausführung des Befehls zu demselben Ergebnis führt, nämlich, dass der Kontostand danach € 1.000,- ist. Der Befehl “erhöhe den Kontostand um € 10,-” wiederum ist nicht idempotent. Wenn der Befehl 5 mal ausgeführt wird, würde der Kontostand nicht um € 10,- erhöht, sondern um € 50,-1).
Man kann den Befehl “erhöhe den Kontostand um € 10,-” idempotent machen, indem man das Argument 2) um eine ID erweitert, den Operanden um die Menge aller angewandten IDs, und die Funktion um eine Prüfung auf das Vorhandensein der ID im Operanden erweitert. Wenn der Befehl also lautet ”{ID=ADD331-EJ944F-411DFI-3311IO}, erhöhe den Kontostand um € 10,-”, der Operand3) eine Liste aller angewendeten IDs hält, dann wird die mehrfache Anwendung des Befehls dazu führen, dass er nur einmal ausgeführt wird, weil die ID nach der ersten Ausführung bereits in der Liste der angewendeten ID enthalten ist.
Diese Methode, einen Befehl idempotent zu machen, hat auch Nebenwirkungen. Die Idempotenz wird dadurch bewirkt, dass Befehle mit einer bereits ausgeführten ID gar nicht mehr ausgeführt werden. Das könnte man dadurch korrigieren, dass nicht alle angewendeten IDs gemerkt werden, sondern immer nur die letzte. In dem Fall gilt f(f(f(f(f(x))))). Aber f(g(f(g(f(x))))) würde anders behandelt werden als in dem Fall der kompletten Liste aller ausgeführten Befehle. Im Fall der Liste wäre das dasselbe wie f(g(g(x))), weil die zweite und dritte Anwendung von f(x) nicht angewendet wird. Wenn aber immer nur die letze ID gemerkt wird, würden alle 5 Befehle ausgeführt werden.
In einem System, dass at-least-once-delivery für Nachrichten garantiert, kann es sein, dass Nachrichten mehrmals beim Command-Handler ankommen. Das kann passieren, weil die Bus Technologie so gebaut ist, oder weil ein Prozess4) nach einem unerwarteten Abbruch den Befehl ein zweites mal sendet.
Ereignisse sind auf natürliche Art idempotent, weil sie immer eine Aggregate ID und eine Version haben5). Event Handler sollten also bei nicht idempotenten Ereignisinhalten auf die ID/Version prüfen6).
Befehle sind nicht auf natürliche Art idempotent7). Auch wenn ein Befehl für eine Aggregate ID und Version gesendet wurde, bedeutet die Kombination [Befehlstyp|Aggregate ID|Version] nicht, dass der Befehl ein und derselbe ist. Ereignisse werden von einem singleton Command Handler erzeugt, da ist das immer der Fall. Befehle können von Anwendern, Sagas und Event Handlern erzeugt werden. Es besteht also die Möglichkeit, dass die Kombination von Typ/ID/Version von mehreren Akteuren verwendet werden kann, und der zweite Befehl mit diesen Werten sollte als Konflikt abgelehnt werden und nicht als bereits erhalten.
Zudem könnten zwei Befehle zum Erzeugen eines Aggregates mit ein und derselben Aggregate ID gesendet werden, vielleicht 10 Jahre auseinander. Der Befehlstyp ist in beiden Fällen auch derselbe, so wie auch die Version8). Der zweite Befehl sollte also nicht einfach übersprungen werden.
Wenn man Befehlskennungen9) für Befehle verwendet, hat man auf jeden Fall die Garantie, dass derselbe Befehl kein zweites mal ausgeführt wird. Es gilt also
at-most-once-handling für Befehle
Das ist gut, denn das ist es ja, was wir erreichen wollten. Die einzige Gefahr, die besteht, ist, dass ein neuer Befehl eine alte ID trifft, und somit gar nicht abgearbeitet wird, weil das System meint, er wurde bereits ausgeführt. Es gilt somit auch
keine Garantie der at-least-once-handling für Befehle
Gewünscht wäre eine exactly-once-handling für Befehle, aber das ist nicht hin zu bekommen, man kann die Wahrscheinlichkeit dafür höchstens so nahe an 110) ran bekommen, wie möglich11).
Die erste Gefahr wurde gerade aufgezeigt. Wenn ein neues Objekt erzeugt werden soll, und zwei Clients, die zeitlich und örtlich weit auseinander liegen können, dieselbe ID für das Aggregate erzeugen, müssen die beiden Befehle als eigene Befehle erkannt werden. Ist das nicht der Fall, wird für den zweiten Befehl das Ergebnis des ersten gemeldet, also
Die Gefahren sind also
Eine Neuanlage eines Aggregates wird als erfolgreich gemeldet, obwohl das nicht stimmt. Hinzu kommt, dass das Aggregate, das man als neu angelegt annimmt, ein ganz anderes ist, und wenn man damit weiter arbeitet, arbeitet man mit dem falschen Aggregate.
Wird der Befehl fälschlicherweise als bereits erhalten angesehen, wird er erneut ausgeführt. Die Frage ist, welchen der beiden Befehle man nun ausführt. Nimmt man den Alten, dann führt man eventuell einen Befehl aus, der gar nicht zu dem aktuellen passt, nimmt man den Neuen, dann bleibt die Möglichkeit, dass ein anderer Prozess den alten Befehl erneut sendet, und dann gemeldet bekommt - Befehl bereits durchgeführt, also zurück zur ersten Gefahrenquelle
Wenn der erste Befehl als abgelehnt angesehen wurde, würde die Neuanlage mit derselben Begründung wie der erste abgelehnt werden.
Dieselben Gefahren bestehen für Änderungen vs. Neuanlagen.
Invarianten der Domäne werden nie verletzt. Wenn andere Regeln, die schließlich eingehalten werden müssen, verletzt werden, werden Event Handler und andere Mechanismen dazu führen, dass die Regeln wieder eingehalten werden.
Aber genau hier liegt der Hund begraben. Wenn ein Event Handler oder eine Saga einen Befehl absendet, der die schließliche Konsistenz herstellen soll, und der Befehl eine bereits verwendete ID hat, würde er kein weiteres mal ausgeführt werden - und die schließliche Konsistenz ist nicht mehr gegeben!
Event Handler und Sagas dürfen die Befehls-IDs selbst erzeugen14). Sie liegen auf der selben Systemebene wie die Endpunkte15), und dürfen Befehle auch direkt auf den Bus packen, ohne über einen Endpunkt zu gehen16). Nennen wir Event Hanlder, Saga, WCF Enpunkte und andere Dienste, die mit dem System kommunizieren können:
Ein Systemagent ist ein Akteur, der ein festes Systemkonto zugewiesen bekommt und dem das System mit der Erzeugung von eindeutigen Befehls-IDs vertraut. Es läuft im Grunde ganz einfach ab. Jeder Systemagent erhält von zentraler Stelle eine eindeutige Kennung17). Dann wird jeder Systemagent in der Lage sein, zu seiner Kennung eindeutige AgentUniqueCommandIDs zu erzeugen. Die Befehls-ID setzt sich somit zusammen aus [UniqueSystemAgentID + AgentUniqueCommandID].
Wenn derselbe Event Handler oder dieselbe Saga - derselbe Agent - partitioniert wird, muss jede einzelne Instanz eine eigene UniqueSystemAgentID erhalten.
Nun werden nur System Agenten erlaubt, Befehls-IDs zu erzeugen. Wenn die Konfiguration korrekt eingehalten wird, und keiner die System Agenten Konten knackt, ist das System absolut sicher. Wenn auch nur einer der beiden Punkte nicht eingehalten wird, ist das System immer noch so ziemlich absolut sicher - so sicher, wie es alle anderen machen, die sich auf eindeutige GUIDs verlassen18).
End-User Clients werden keine eigenen Befehls-IDs erzeugen. Wenn ein Event Handler einen Befehl abschickt, muss er bei unerwarteten Fehlern die Möglichkeit haben, den Befehl idempotent nochmal abzuschicken. Er kann nicht einfach gucken was passiert ist und dann entsprechend handeln19). Wenn ein End-User einen Befehl abschickt und ein unerwarteter Fehler passiert, startet der End-User die Anwendung neu, guckt sich die Daten an, und handelt dann entsprechend.
Der End-User erhält vom Endpunkt20) aber die vom Endpunkt21) erzeugte und im Befehl eingesetzte CommandID22). Diese ID kann er dann verwenden um z.B. danach zu Pollen.
Die einzige Gefahr hierbei ist, dass der Befehl vom Endpunkt ausgeführt wird, die ID den Client vom End-User nicht erreicht, weil die Verbindung weg ist. Auch das ist lösbar.
Dieser Workflow
Der oben genannte Workflow ist umständlich, und sollte nicht von jedem Client für jeden Befehl so umgesetzt werden. Er ist nur wichtig, wenn der Client bei Fehlern überprüfen muss, ob ein Befehl abgesendet wurde bevor er ihn nochmal verschickt. In der Regel reicht der einfache Workflow, in dem der Client die Befehls-ID zurück erhält, oder in dem sich der Client gar nicht um die Befehls-ID kümmert völlig aus.
Das System kann so gebaut werden, dass Agenten von Außen Befehls-IDs nicht selbst bestimmen können, und die Agenten von innen über UniqueAgentIDs und der sicheren Erzeugung von eindeutigen IDs zu der eigenen AgentID immer nur eindeutige IDs erzeugen. Das mit “von Außen” und “von Innen” ist nur eine Abstraktion. Das wird über das Rechtesystem geregelt. Der Command Handler, bei dem Befehle letztendlich ankommen, prüft ob der Sender das Recht hat, Befehls-IDs zu vergeben. Event Handler und Sagas können Befehle mit IDs versenden, wenn sie im Kontext eines entsprechenden Systemkontos laufen. WCF Endpunkte auch. Fremdsysteme auch, wenn man ihnen vertraut und ein eigenes Konto gibt24).