User Tools

Site Tools


concepts:idempotency

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

concepts:idempotency [2013/01/15 20:39]
rtavassoli
concepts:idempotency [2013/01/18 13:26] (current)
rtavassoli
Line 2: Line 2:
 [[http://de.wikipedia.org/wiki/Idempotenz|Wikipedia]] sagt [[http://de.wikipedia.org/wiki/Idempotenz|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. > 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ühren, 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, wurde der Kontostand nicht um € 10,- erhöht, sondern um € 50,-((<nowiki>f(f(f(f(f(x))))) != f(x)</nowiki>)).+Der Befehl "setze den Kontostand auf € 1.000,-" ist idempotent, weil die mehrfache Ausführung des Befehls zu demselben Ergebnis führt, nämlichdass 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,-((<nowiki>f(f(f(f(f(x))))) != f(x)</nowiki>)).
 \\ \\ \\ \\
 Man kann den Befehl "erhöhe den Kontostand um € 10,-" idempotent machen, indem man das Argument ((den Befehl)) 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 Operand((das Aggregate)) 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. Man kann den Befehl "erhöhe den Kontostand um € 10,-" idempotent machen, indem man das Argument ((den Befehl)) 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 Operand((das Aggregate)) 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 <nowiki>f(f(f(f(f(x)))))</nowiki>. Aber <nowiki>f(g(f(g(f(x)))))</nowiki> 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 <nowiki>f(g(g(x)))</nowiki>, 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. 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 <nowiki>f(f(f(f(f(x)))))</nowiki>. Aber <nowiki>f(g(f(g(f(x)))))</nowiki> 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 <nowiki>f(g(g(x)))</nowiki>, 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.
-===== Natürliche Idempotenz ===== +===== Befehlsidempotenz - oder lieber //Eindeutigkeit// ===== 
-Sind Befehle zur Neuanlage von //aggregates// oder //entities// auf natürliche Weise idempotent? Wenn ich zwei mal den Befehl zur Anlage einer Person sende, beide mit derselben Personen-ID((Guid)), ist das dann ein idempotenter Befehl? Die Antwort ist, dass es darauf ankommt. Wenn das Ergebnis des zweiten Befehls ist, dass nichts passiert, dann ist der Befehl idempotent. Wenn das Ergebnis des zweiten Befehls ist, dass ein Fehler ausgelöst wird, ist der Befehl nicht idempotent, weil es den Fehler beim ersten Mal nicht gab. +In einem schwach zusammenhängenden System((loosely coupled system)), das asynchron kommuniziert, ist es äußerst wichtig, dass Befehle idempotent sind. Wenn ein Prozess einen Befehl absendet, muss er die Gewissheit haben, dass der Befehl //schließlich//((eventually)) ankommtentweder indem er den Empfänger abfragen kann oder den Befehl einfach nochmal sendet mit der Gewissheitdass er nur einmal ausgeführt wird. Das Thema der [[technology:domainmodel:command:commanduniqueness|Befehlseindeutigkeit]] ist somit ein sehr wichtiges. 
-\\ \\ +
-Wie also damit umgehen? Auch wenn die Wahrscheinlichkeit verschwindend gering ist, dass eine Guid zwei mal erzeugt wird, und dann noch auf dieselbe Funktion angewendet wird, halte ich es für wichtig, dass der zweite Befehl nicht stillschweigend ignoriert wird, denn was ist, wenn es gar nicht beabsichtigt war dieselbe Guid zu verwenden? Ich sehe die Idempotenz somit an anderer Stelle. +
-\\ \\ +
-Ein Command Handler behandelt einen Befehl indem im Aggregate eine Methode aufgerufen wird. Es werden also zwei Funktionen aufgerufen, einmal eine im Command Handler, dann eine im Aggregate. Die Methoden im Aggregate sollten immer ausgeführt werden und Konflikte als solche melden. Der Command Handler hingegen sollte die Möglichkeit haben, Befehle als bereits erhalten zu kennzeichnen. In unsicheren Bus-Technologien gilt oft die Regel //at-least-once-delivery//((Der serialisierte Befehl wird mindestens einmal geliefert. Es kann aber passieren, dass er mehrmals geliefert wird. Wenn zwischen den Lieferungen andere Befehle liegen, dann reicht die Idempotenz der Befehle in der Domäne nicht aus, denn wenn "setze Konto auf 100" zwei mal kommt, "setze Konto auf 50" danach abgesendet wurde, aber zwischen den beiden Befehlen ankommt, weil der Absender des ersten das Ergebnis nicht gesehen hat und ihn nochmal absendet, ist das Ergebnis falsch.)). Also ist die einmalige Behandlung von Befehlen schon durch den((möglichen)) Einsatz von Bus-Technologien bedingt. +
-==== Wie mache ich einen Befehl idempotent? ==== +
-Ein Befehl wird für ein Aggregate((oder für einen Domain Service, dann das //Hauptaggregate// verwenden)) abgesendet. Er kann für eine bestimmte Version bestimmt seinmuss es aber nicht. Einfach eine GUID als eindeutige ID zu verwenden halte ich für zu //unsicher//. Wenn das System weite Anwendung hatkönnen mehrere Millionen Befehle von diversen Clients - nicht immer unter direkter/eigener Kontrolle - versendet werden. In dem Fall ist die Wahrscheinlichkeit, dass ein //neuer// Befehl eine bereits verwendete Idempotenz ID verwendet, vielleicht signifikant. +
-\\ \\ +
-Ich schlage somit vor, dass die Idempotenz ID (idempotency ID) eine einfache GUID ist. Sie muss aber nicht eindeutig sein! Um zu prüfen, ob der Befehl bereit erhalten wurde, fragt der Server alle erhaltene Befehle mit dieser GUID ab. Das sollten i.d.R. keine oder eine sein. Zu der Guid wird der Befehl als serialisiertes JSON Objekt gespeichert. Wenn zu der ID ein oder mehrere Befehle gefunden werden, werden die Inhalte der Befehle gegen den Inhalt des gesendeten Befehls verglichen. Wenn neben der Idempotenz ID auch noch die Inhalte dieselben sind((wobei Befehlsversionen berücksichtigt werden müssen - d.h. vielleicht gab es eine Systemumstellung und die Saga sendet denselben Befehl in einer neuen Version. In dem Fall muss die Gleichheit trotzdem sicher gestellt sein)), dann gilt der Befehl als bereits erhalten und verarbeitet. +
-\\ \\ +
-Das muss an Sicherheit ausreichen. Der Befehl wird mindestens eine Aggregate ID enthalten, eventuell noch eine Versionsnummer und weitere Parameter((ein Zeitstempel kann auch noch dazu kommen, d.h. der Client setzt automatisch einen Zeitstempel)). Wenn ein Client nun einen Befehl abschickt und möchtedass dieser idempotent verarbeitet wird((er muss ja gar keine Idempotenz ID setzen)), hat er +
-  * die Sicherheit, dass wenn //er// den Befehl so mehrmals schickt, der Befehl garantiert maximal einmal verarbeitet wird, +
-  * ausreichende Sicherheit, dass die Idempotenz ID + Aggregate ID + Befehlstyp + Befehlsparameter in dieser Kombination niemals doppelt erzeugt werden. Und wenn doch, wird der Befehl als bereits verarbeitet gemeldet, am besten mit dem Zeitpunkt, wann das war((man kann auch definieren, dass gefundene Befehle, die älter als ein Tag sind, ignoriert werden)). Erstens ist die Wahrscheinlichkeit so gering, dass sogar ich damit kein Problem mehr habe. Und zweitens: Was kann da im schlimmsten Fall passieren? Dann schick eine neue Version des Befehls ab, mit einer neuen Idempotenz ID, und der Befehl wird durchgeführt. Einen Befehl nicht auszuführen ist doch keine Gefahr für die Konsistenz des Systems. +
-Zusammengefasst wird eine //maximal einmalige Ausführung// des Befehls garantiert. Das ist es ja genau, was wir erreichen wollen, d.h. die Idempotenz wurde für genau diesen Zweck mit gesendet. Die Wahrscheinlichkeit einer falschen Doppelerkennung wird zudem soweit es geht reduziert, dass ich jede Wette abschließe, dass Clients, die sich an die Regeln halten((u.a. beim Erzeugen von GUIDs)), in einer Millionen Jahren keine doppelte Idempotenz ID + Befehl((Typ + Zustand)) erzeugen. +
-=== Eine Gefahrenquelle === +
-Befehle werden gespeichert noch bevor sie verarbeitet werden, also auch bevor sie authentifiziert wurden. Das öffnet möglichen Angreifern die Tür, einfach alle möglichen nicht authorisierten Befehle zu versenden, um die Liste der bereits erhaltenen Befehle mit Müll zu füllen. Somit sollten Befehle erst in die Liste geschrieben werdennachdem sie authentifiziert((nicht //authorisiert//)) wurden, und dann auch zusammen mit dem SecuritiyPrincipal((Der Identität des Absenders)),((Idempotenz kann also nur von Clients verlangt werden, die sich auch korrekt angemeldet haben)),((Das reduziert die Wahrscheinlichkeit, dass 2 getrennte Befehle als gleich angesehen werden, nochmal um den Faktor <nowiki>Anzahl(Benuzterkonten + Systemkonten)</nowiki>)). Klar, jemand kann einen Client programmieren, der sich erst authentifiziert und dann Müll versendet. Aber diesen Client muss dann auch ein Anwender verwenden, der sich erst mit korrekte Anmeldedaten anmeldetDas wird dann gelöst, wenn es passiert - also nie ;-)+
concepts/idempotency.1358278760.txt.gz · Last modified: 2013/01/15 20:39 by rtavassoli