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/16 16:12]
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.
-===== Befehlsidempotenz ===== +===== Befehlsidempotenz - oder lieber //Eindeutigkeit// ===== 
-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 Prozess((Saga oder UI Client)) nach einem unerwarteten Abbruch den Befehl ein zweites mal sendet. +In einem schwach zusammenhängenden System((loosely coupled system)), das asynchron kommuniziertist es äußerst wichtigdass Befehle idempotent sind. Wenn ein Prozess einen Befehl absendetmuss er die Gewissheit haben, dass der Befehl //schließlich//((eventually)) ankommt, entweder indem er den Empfänger abfragen kann oder den Befehl einfach nochmal sendet mit der Gewissheit, dass er nur einmal ausgeführt wird. Das Thema der [[technology:domainmodel:command:commanduniqueness|Befehlseindeutigkeit]] ist somit ein sehr wichtiges.
-\\ \\ +
-Ereignisse sind auf natürliche Art idempotentweil sie immer eine Aggregate ID und eine Version haben((und einen Ereignistypder den Typ des Aggregates eindeutig bestimmt)). Event Handler sollten also bei nicht idempotenten Ereignisinhalten auf die ID/Version prüfen((oder auf die Publisher Sequenzdie einfach pro Veröffentlicher eine laufende Nummer ist)). +
-\\ \\ +
-Befehle sind nicht auf natürliche Art idempotent((außer möglicherweise die Auswirkung des Befehls))Auch wenn ein Befehl für eine Aggregate ID und Version gesendet wurdebedeutet 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 Version((Ein Befehl zum Erzeugen eines Aggregats hat keine Versionsnummer, bzw. die Version 0)). Der zweite Befehl sollte also nicht einfach übersprungen werden. +
-==== Gefahren bei fehlerhafter Befehlsidempotenz ==== +
-Wenn man Befehlskennungen((ID)) 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 1((100%)) ran bekommen, wie möglich((Wenn alle Clients sich korrekt verhalten, kann die Wahrscheinlichkeit bei Fehlerfreiheit des Systems 1 werden; wir wollen das System aber nicht gegen neue Clients schließen)). +
-\\ \\ +
-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 +
-  * Befehl erfolgreich durchgeführt +
-  * Befehl erhalten((Ein Befehl kann auch in diesem Zustand //hängen bleiben// wenn er aus unerwarteten technischen Gründen nicht durchgeführt werden kann und der Sender nach dem //fire-and-forget// Prinzip nicht versucht, den Fehler zu beheben und den Befehl erneut zu senden)) +
-  * Befehl abgelehnt((aus Business Gründen, z.B. nicht autorisiert, nicht validiert, oder andere Verletzungen der Geschäftsregeln)) +
-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. 
-==== Auswirkungen bei nicht abgefangenen Konflikten ==== 
-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. Das System kann also im Grunde niemals in einen anhaltenden inkonsistenten Zustand versetzt werden. Aber gucken wir uns das im Einzelnen an 
-=== Event Handler === 
-Wenn ein Event Handler ein Ereignis abfängt und als Ergebnis einen Befehl ausführen soll, und dieser als //bereits erhalten// erkannt wird, würde der Befehl nicht ausgeführt werden. Das ist einfach mit dem zweiten Punkt der im folgenden Abschnitt beschriebenen Gefahrenabwehr zu lösen. Event Handler laufen im Kontext eines Systemkontos. Das Konto ist Teil der Prüfung auf die Identität des Befehls, also kann es zu keinen nicht erkannten Konflikten mit den Befehlen anderer Anwender kommen((Höchstens zu DuplicateCommandException, dann sendet der Event Handler den Befehl einfach mit einer neuen ID ab)). Der Event Handler merkt sich also einfach die Befehls-IDs, die er erzeugt und verwendet um sicher zu stellen, dass dieselbe ID kein zweites mal verwendet wird. So kann es niemals zu unerkannten Konflikten führen - so lange alle Event-Handler, die dieselben Befehlstypen versenden, sich daran halten; und das Systemkonto nicht geknackt wird und von jemanden anderen missbraucht wird. 
----- 
-Was ist mit Systempartitionen? Wenn derselbe Event Handler auf zwei unterschiedlichen Rechnern läuft, ist die eindeutige BefehlsID Erzeugung pro Systemkonto und EventHandler nicht mehr gegeben. Die Befehls-ID muss also die Partitions-ID beinhalten! 
-> Stimmt nicht! 
-Ein Befehl erhält einfach ein weiters Feld //Partition//((dabei ist die Partition des Absenders gemeint)). Anstatt die Partition neben der Befehls-ID in den PK des Befehls aufzunehmen, wird die Partition zur Überprüfung für die DuplicateCommandException verwendet. Wenn alles andere gleich ist, die Partition aber unterschiedlich, wird diese Exception ausgelöst. Dann weiß der Absender, dass die ID bereits vergeben wurde und setzt eine neue((Die Partitionierung der Event Handler muss nicht mit der Partitionierung der Command Handler übereinstimmen. Deshalb ist das notwendig)). 
-\\ \\ 
-Was bringt uns das? Das SELECT auf die bereits erhaltenen Befehle soll einfach gehalten werden, und wenn man auf ein Feld abfragt, ist das wesentlich schneller als auf einen kombinierten Schlüssel. Ein Konflikt kostet nun zwar mehrere //round trips// anstatt, dass die neue BefehlsID/Partition-Kombination einfach so akzeptiert wird. So ein Konflikt sollte aber nur alle Jubeljahre passieren, dann ist ein round trip nicht so teuer((der Programmieraufwand schon, aber das ist der Preis der Idempotenz, finde ich)). 
-\\ \\ 
-Da ja auch das SecurityPrincipal des authentifizierten Users für die DuplicateCommandException geprüft wird, könnte man pro Event Handler Partition ein eigenes Konto verwenden. Wofür man sich letztendlich entscheidet ist egal, beide Mechanismen werden in den Command Handlern eingebaut - man muss sich mindestens für einen entscheiden. 
----- 
-Warum ist das so wichitg? Angenommen ein Event Handler synchronisiert zwei Bounded Contexts. Wenn im ersten eine Änderung passiert, muss sie schließlich im zweiten ankommen. Wenn der Befehl idempotent ist((z.B. ändere den Namen in "Max Mustermann")) ist das alles kein Problem, dann kann der Event Handler den Befehl so oft absenden, bis er erfolgreich ist, ohne eine eigene Befehls-ID zu setzen((das übernimmt dann der Server. Die Prüfung auf Idempotenz findet dann gar nicht statt. Wobei unbedingt noch ein Flag //Server Generated ID// gesetzt werden muss, welches bei sonstiger Gleichheit aller Werte eines neuen Befehls mit ID zu einer DuplicateCommandException führen muss)). 
-\\ \\ 
-Ist der Befehl aber nicht intrinsisch idempotent, muss eine Befehls-ID vom Event Handler gesetzt werden. Das darf aber nicht dazu führen, dass der Befehl nicht ausgeführt wird((vielleicht wurde die Person schon mal in Max Mustermann umbenannt, inzwischen aber in Max Musterkerl, und soll jetzt aber wieder Mustermann heißen)). Wenn er nicht ausgeführt wird, werden zwar keine Geschäftsregeln verletzt, das System wäre aber nicht //schließlich konsistent//, weil die Änderung nie ankommt. 
-\\ \\ 
-Das ist noch wichtiger bei Event Handlern, die tatsächliche Aggregate übergreifende Geschäftsregelverletzungen kompensieren sollen. 
-\\ \\ 
-OK, die Wahrscheinlichkeit, dass so was passiert, wenn man einfache GUIDs verwendet, ist so gering, dass ein manueller Eingriff alle 10 Jahre akzeptabel wäre. Aber ich finde, die Sicherheitsmechanismen zur Gefahrenabwehr sind nicht so teuer. 
-=== Sagas === 
-Sagas verwenden eine //CorrelationID// um den Prozess zu koordinieren. Wenn die Saga die ID sicher erzeugt((bereits verwendete CorrelationIDs speichern und bei Konflikten eine neue erzeugt)), dann ist die Kombination [SagaTyp|PartitionID((Wenn dieselbe Saga auf mehreren Partitionen läuft, erhält jede Partition einfach eine eindeutige ID. Daran muss man natürlich denken!))|CorrelationID] eindeutig. 
-\\ \\ 
-Die BefehlsIDs haben mit der CorrelationID aber nichts zu tun. Bei der Überprüfung auf eine DuplicateCommandException kann die CorrelationID mit ran gezogen werden. Das muss aber nicht passieren, wenn die Saga die BefehlsID genauso sicher erzeugt wie die Event Handler. 
-=== Endanwender === 
-Bei korrekter Konfiguration und entsprechendem Schutz der Systemkonten können die Befehlskennungen, die von Event Handlern und Sagas gesetzt werden, eindeutig gemacht werden, bzw. können Konflikte((DuplicateCommandException)) mit absoluter Sicherheit gelöst werden((pro Command Handler Partition, versteht sich)). Das funktioniert, weil man die Systemkonto/Partition Kombination, die von den Event Handlern und Sagas verwendet werden, eindeutig machen kann, und schützen kann, so dass sie von keinem anderen Client verwendet werden kann. 
-\\ \\ 
-Wie kann man das für Endanwender erreichen, die unterschiedliche Clients verwenden können? Muss man diesen Grad an Sicherheit überhaupt für Endanwender erreichen? 
-> Befehle von Endanwendern benötigen nicht den Grad an garantierter Sicherheit der Eindeutigkeit wie die von Event Handlern oder Sagas. Event Handler und Sagas garantieren u.a. die schließliche Konsistenz, daher sollte diese Garantie so gut es geht sicher gestellt werden. Endanwender sind da ein wenig flexibler und treffen Entscheidungen nicht nach fest gelegten Schemata, sondern nach Bedarf. 
-Ich nehme an, dass ein Benutzerkonto auch Personengebunden verwendet wird. Somit wird i.d.R. immer nur ein Benutzerkonto gleichzeitig angemeldet sein((single login werde ich aber nicht erzwingen)). Wenn man im Befehl also noch den Zeitstempel der Befehlserzeugung setzt, kann davon ausgegangen werden, dass die Kombination [Befehls-ID|Benutzerkonto|Zeitstempel|Befehlsinhalt] keine zweimal erzeugt werden. 
-\\ \\ 
-Nicht vergessen, die Befehls-ID sollte global eindeutig sein. Es geht nun nur noch um den möglichen Fall, dass dieselbe Befehls-ID ein zweites Mal erzeugt wurde. Nur in diesem Fall werden noch die weiteren Daten [Benutzerkonto|Zeitstempel|Befehlsinhalt] geprüft, um sicher zu stellen, ob es wirklich derselbe Befehl war, oder ob es ein Konflikt der ID darstellt. Wenn es sich um einen Konflikt handelt, erhält der Client eine DuplicateCommandException und muss nur die ID ändern und den Befehl erneut abschicken. 
-\\ \\ 
-== Asynchrone Befehle == 
-Wenn Befehle nicht sofort behandelt werden, sondern einfach mit Bus.Send(command) versendet werden, weiß man nicht, ob der Command Handler den Befehl akzeptiert oder nicht. Man muss das Ergebnis [[technology:polling|Pollen]]. ABER WOMIT? Polling ID? Was ist mit deren Eindeutigkeit? Das Problem ist, dass wenn der command vom Bus an den Command Handler übergeben wird, die Antwort  
- 
-==== Gefahrenabwehr ==== 
-Als erstes wird neben der ID des Befehls der Inhalt des Befehls verglichen. Wenn die ID dieselbe ist, der Inhalt aber ein anderer, dann erhält der Client eine eigens dafür definierte DuplicateCommandException. Er weiß dann, dass er zufällig eine bereits verwendete ID getroffen hat, dass aber der Inhalt des Befehls ein anderer ist. Er sendet den Befehl einfach ein weiteres mal ab nachdem die ID ersetzt wurde. 
-\\ \\ 
-Zweitens wird der Command Handler Befehle nur speichern und ausführen, wenn der Anwender authentifiziert ist. Das SecurityPrincipal wird auch zu dem Befehl und der ID gespeichert, und wenn diese nicht übereinstimmt, wird ebenfalls ein DuplicateCommandException ausgelöst((Wenn derselbe Befehl von jemanden anderen gesendet wurde, geht das System davon aus, dass es zwei unterschiedliche Befehle sein sollten, und meldet bei ein und derselben Befehlskennung einen Konflikt)). So können Angreifer zudem nicht einfach sinnlose Befehle senden um die IDs zu füllen. 
-\\ \\ 
-Drittens wird eine mitgesendete PartitionID im Befehl genauso verwendet wie das SecurityPrincipal des authentifizierten Users: Bei gleicher Befehls-ID aber unterschiedlicher PartitionID eine DuplicateCommandException melden. Das ist für Event Handler und Sagas, die verteilt werden, wichtig. 
-===== 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-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. 
-\\ \\ 
-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 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 signifikant((eher nicht, hängt aber auch u.a. von dem GUID Erstellungsverfahren ab)). 
-\\ \\ 
-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öchte, dass 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 werden, nachdem 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 anmeldet. Das wird dann gelöst, wenn es passiert - also nie ;-) 
concepts/idempotency.1358349145.txt.gz · Last modified: 2013/01/16 16:12 by rtavassoli