User Tools

Site Tools


concepts:idempotency

This is an old revision of the document!


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.

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

Natürliche Idempotenz

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-ID4), 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.

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-delivery5). Also ist die einmalige Behandlung von Befehlen schon durch den6) Einsatz von Bus-Technologien bedingt.

Wie mache ich einen Befehl idempotent?

Ein Befehl wird für ein Aggregate7) abgesendet. Er kann für eine bestimmte Version bestimmt sein, muss es aber nicht. Einfach eine GUID als eindeutige ID zu verwenden halte ich für zu unsicher. Wenn das System weite Anwendung hat, kö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 signifikant8).

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 sind9), 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 Parameter10). Wenn ein Client nun einen Befehl abschickt und möchte, dass dieser idempotent verarbeitet wird11), 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 war12). 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 halten13), in einer Millionen Jahren keine doppelte Idempotenz ID + Befehl14) 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 werden, nachdem sie authentifiziert15) wurden, und dann auch zusammen mit dem SecuritiyPrincipal16),17),18). 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 anmeldet. Das wird dann gelöst, wenn es passiert - also nie ;-)

1) f(f(f(f(f(x))))) != f(x)
2) den Befehl
3) das Aggregate
4) Guid
5) 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.
6) möglichen
7) oder für einen Domain Service, dann das Hauptaggregate verwenden
8) eher nicht, hängt aber auch u.a. von dem GUID Erstellungsverfahren ab
9) 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
10) ein Zeitstempel kann auch noch dazu kommen, d.h. der Client setzt automatisch einen Zeitstempel
11) er muss ja gar keine Idempotenz ID setzen
12) man kann auch definieren, dass gefundene Befehle, die älter als ein Tag sind, ignoriert werden
13) u.a. beim Erzeugen von GUIDs
14) Typ + Zustand
15) nicht authorisiert
16) Der Identität des Absenders
17) Idempotenz kann also nur von Clients verlangt werden, die sich auch korrekt angemeldet haben
18) Das reduziert die Wahrscheinlichkeit, dass 2 getrennte Befehle als gleich angesehen werden, nochmal um den Faktor Anzahl(Benuzterkonten + Systemkonten)
concepts/idempotency.1358331883.txt.gz · Last modified: 2013/01/16 11:24 by rtavassoli