This shows you the differences between two versions of the page.
technology:domainmodel:command:commanduniqueness [2013/02/04 21:19] rtavassoli |
technology:domainmodel:command:commanduniqueness [2013/02/06 11:34] (current) rtavassoli |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Befehlseindeutigkeit ====== | ====== Befehlseindeutigkeit ====== | ||
- | 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)) 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 Befehlseindeutigkeit ist somit ein sehr wichtiges. | + | 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)) 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 Befehlseindeutigkeit ist somit ein sehr wichtiges((Eigentlich //Befehlsidempotenz//. In den Fällen, in denen ein Befehl nicht in sich idempotent ist, also in dem <nowiki>f(x) != f(f(x))</nowiki> ist, muss die Idempotenz über die Eindeutigkeit hergestellt werden)). |
\\ \\ | \\ \\ | ||
Befehle können mehrmals beim Command-Handler ankommen. Das liegt einmal an der eingesetzten Bus-Architektur, die in diesem Fall //at-least-once-delivery// für Nachrichten garantiert. Das kann aber auch daran liegen weil ein Prozess((Saga oder UI Client)) nach einem unerwarteten Abbruch den Befehl ein zweites mal sendet. | Befehle können mehrmals beim Command-Handler ankommen. Das liegt einmal an der eingesetzten Bus-Architektur, die in diesem Fall //at-least-once-delivery// für Nachrichten garantiert. Das kann aber auch daran liegen weil ein Prozess((Saga oder UI Client)) 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 haben((und einen Ereignistyp, der den Typ des Aggregates eindeutig bestimmt)). Event Handler sollten also bei nicht idempotenten Ereignisinhalten auf die ID/Version prüfen((oder auf die Publisher Sequenz, die einfach pro Veröffentlicher eine laufende Nummer ist)). | > Ereignisse sind auf natürliche Art idempotent, weil sie immer eine Aggregate ID und eine Version haben((und einen Ereignistyp, der den Typ des Aggregates eindeutig bestimmt)). Event Handler sollten also bei nicht idempotenten Ereignisinhalten auf die ID/Version prüfen((oder auf die Publisher Sequenz, die 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 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//. | + | 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 wurde, bedeutet die Kombination [Befehlstyp|Aggregate ID|Version] nicht, dass der Befehl ein und derselbe ist. Ereignisse werden von dem Aggregate 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//. |
===== Ziel der Befehlseindeutigkeit ===== | ===== Ziel der Befehlseindeutigkeit ===== | ||
- | Befehle müssen eindeutig gemacht werden, damit sie in einem System, dass sie //garantiert schließlich liefert//, sie dabei aber //eventuell mehrmals liefert//, nur genau einmal ausgeführt werden. Somit muss der Command Handler((der den Befehl letzten Endes ausführt)) sich merken, welche Befehl er bereits ausgeführt hat, und wenn er einen Befehl ein zweites Mal erhält, kann er prüfen, ob er bereits ausgeführt wurde oder nicht. | + | Befehle müssen eindeutig gemacht werden, damit sie in einem System, das sie //garantiert schließlich liefert//, sie dabei aber //eventuell mehrmals liefert//, nur genau einmal ausgeführt werden. Somit muss der Command Handler((der den Befehl letzten Endes ausführt)) sich merken, welche Befehl er bereits ausgeführt hat, und wenn er einen Befehl ein zweites Mal erhält, kann er prüfen, ob er bereits ausgeführt wurde oder nicht. |
\\ \\ | \\ \\ | ||
- | Über eine Befehlseindeutigkeit kann man einen Teil der Abmachung garantieren: **Ein Befehl wird niemals mehrmals ausgeführt, also maximal einmal**. Eine Saga kann also denselben Befehl eine Million mal abschicken, er wird nur einmal ausgeführt. Was aber nicht garantiert werden kann, ist das ein Befehl **mindestens einmal ausgeführt wird**. | + | Über eine Befehls-ID((oder Idempotenz-ID)) kann man einen Teil der Abmachung garantieren: **Ein Befehl wird niemals mehrmals ausgeführt, also maximal einmal**. Eine Saga kann also denselben Befehl eine Million mal abschicken, er wird nur einmal ausgeführt((weil der Command Handler einen Befehl mit einer ID, für die bereits ein Befehl ausgeführt wurde, kein zweites mal ausführt)). Was aber nicht garantiert werden kann, ist das ein Befehl **mindestens einmal ausgeführt wird**. |
> Es besteht die Gefahr, dass ein neuer Befehl vom Command Handler als bereits erhalten angesehen wird, weil er mit einem vorherigen identisch ist, obwohl es sich eigentlich um einen neuen Befehl handelt. | > Es besteht die Gefahr, dass ein neuer Befehl vom Command Handler als bereits erhalten angesehen wird, weil er mit einem vorherigen identisch ist, obwohl es sich eigentlich um einen neuen Befehl handelt. | ||
- | Diese Gefahr muss beseitigt werden, vor allem für Prozesse((die von Sagas durchgeführt werden)) und Event Handler, die verantwortlich dafür sind, schließliche Konsistenz herzuführen, ansonsten wäre das System dann nur schließlich Konsistent wenn der User eingreift nachdem er erkennt, dass es seinen Job nicht macht. | + | Diese Gefahr muss beseitigt werden, vor allem für Prozesse((die von Sagas durchgeführt werden)) und Event Handler, die verantwortlich dafür sind, schließliche Konsistenz herbei zu führen. Würden Befehle nicht durchgeführt werden, kann diese schließliche Konsistenz nicht mehr garantiert werden. Die Gefahr wird dadurch beseitigt, indem man den Befehl global((räumlich und zeitlich)) eindeutig macht. |
- | Die Gefahr wird dadurch beseitigt, indem man den Befehl eindeutig macht. | + | |
==== Wie mache ich den Befehl eindeutig ==== | ==== Wie mache ich den Befehl eindeutig ==== | ||
- | Es scheint, als wäre eine GUID für viele ausreichend. Es wird sich darauf verlassen, dass die GUID global eindeutig ist. Wenn die GUIDs nach einiger Zeit auch noch //verfallen//, ist die Wahrscheinlichkeit so gering, dass dieselbe GUID für zwei unterschiedliche Befehle verwendet wird, dass es unwirtschaftlich ist, komplexere Lösungen zu bauen. | + | Es scheint, als wäre eine GUID als Idempotenz-ID für Befehle für viele ausreichend. Es wird sich darauf verlassen, dass die GUID global eindeutig ist. Wenn die GUIDs nach einiger Zeit auch noch //verfallen//, ist die Wahrscheinlichkeit so gering, dass dieselbe GUID für zwei unterschiedliche Befehle verwendet wird, dass es unwirtschaftlich ist, komplexere Lösungen zu bauen. |
\\ \\ | \\ \\ | ||
Ich meine, man kann komplexere Lösungen bauen, die kaum etwas zusätzliches kosten, das System aber noch robuster machen((Murphys Gesetz sagt ja, dass, wenn etwas schief läuft, es so richtig schief läuft - frei gedeutet)). Für mich gilt somit erstens | Ich meine, man kann komplexere Lösungen bauen, die kaum etwas zusätzliches kosten, das System aber noch robuster machen((Murphys Gesetz sagt ja, dass, wenn etwas schief läuft, es so richtig schief läuft - frei gedeutet)). Für mich gilt somit erstens | ||
- | > Ein Befehl wird nicht nur über die ID identifiziert, sondern auch über den Absender, das Datum/Uhrzeit und den Inhalt. | + | > Ein Befehl wird nicht nur über die ID identifiziert, sondern auch über den authentifizierten Absender. Es wird dann darauf vertraut, dass der Absender darauf achtet, dass //seine// Befehle eine eindeutige ID erhalten. Da die Idempotenz-ID eine Kombination aus dem Absender und der vom Absender erzeugten ID ist, wäre der Befehl global eindeutig. |
- | Wenn die ID auch noch frei konfigurierbar ist((ein ausreichend großer String)), dann sind alle Systemanforderungen für eine fast lückenlose Sicherheit gegeben((vorausgesetzt, dass System wird nicht gehackt)). | + | === Wer ist verantwortlich === |
- | === Was identifiziert einen Befehl === | + | Wenn ich als Client eindeutige Befehle versende, möchte ich davon ausgehen können, dass sie auch als solche erkannt werden. Ich kann mich dabei nicht darauf verlassen, dass sich alle Clients korrekt verhalten, vor allem dann nicht, wenn die Clients nicht alle aus einer Hand kommen. Daher ist der Server dafür verantwortlich, das Verhalten der Clients zu überprüfen. Dafür muss der Server dem Client vertrauen können. Die erste Regel ist somit |
- | Der Befehl wird identifiziert über | + | > Der Server akzeptiert idempotente Befehle nur von Clients, die vom Server identifiziert werden können. |
- | * das authentifizierte Konto((d.h. dass Befehle auch nur vom Command Handler gespeichert und danach ausgeführt werden, wenn sie authentifiziert wurden)), also //von wem// stammt der Befehl? | + | Das macht man entweder, indem sich der Client gegen den Server authentifiziert, z.B. über ein Zertifikat. Das klappt aber nicht, wenn die Befehle über einen Bus versendet werden. |
- | * den Zeitstempel, also //wann wurde// der Befehl gegeben, | + | Alternativ kann man das über die mit gesendeten Authentifizierungsdaten des Users machen. Wenn man sicher sein kann, dass nur ein bestimmter Client die Anmeldedaten eines bestimmten Users kennt, dann kann sich der Client darüber dem Server gegenüber identifizieren. Eine Saga kann z.B. ein bestimmtes Systemkonto verwenden, dessen Anmeldedaten nur der Saga bekannt sind. Kommt ein Befehl mit den Anmeldedaten dieses Kontos an, kann der Server davon ausgehen, dass der Befehl von der Saga kommt. |
- | * die ID, also //welche Kennung// hat der Befehl, | + | |
- | * und den Inhalt, also //wie lautet// der Befehl. | + | |
- | Wenn ein Befehl also ankommt, wird er authentifiziert. Dann wird geprüft, ob es die ID bereits gibt((d.h. die ID reicht als lookup/SELECT Element)). Gibt es sie bereits((vielleicht mehr als einmal)), werden alle anderen Aspekte des Befehls geprüft. Nur, wenn er noch nicht ausgeführt wurde, wird er zu der ID serialisiert gespeichert und danach ausgeführt((das Ergebnis der Ausführung wird dann zu dem Befehl geschrieben - synchron mit den Ereignissen, die als Folge des Befehls ausgelöst wurden, d.h. hier läuft eine Transaktion)). | + | |
\\ \\ | \\ \\ | ||
- | Gibt es die ID((Guid)) bereits, aber die anderen Aspekte stimmen nicht überein, muss das System davon ausgehen, dass es nicht derselbe Befehl ist. Den Befehl in dem Fall einfach aus zu führen - es ist ja ein neuer - ist nicht korrekt. Die ID muss als Primärschlüssel dienen. Ansonsten besteht die Gefahr, dass derselbe Befehl parallel mehrmals gesendet wird. In beiden Fällen würde er bei der Suche nach der ID nicht gefunden werden, und würde somit zwei mal verarbeitet werden. Wenn die ID kein Primärschlüssel ist, würde das klappen, d.h. die doppelte Verarbeitung würde erlaubt werden und nicht erkannt werden - schlecht wenn der Befehl nicht in sich idempotent ist. | + | Wenn diverse Clients gebaut werden, und das von unterschiedlichen, unabhängigen Firmen, kann es durchaus sein, dass der Algorithmus für die Erzeugung der IDs, die die Firmen für die Befehlseindeutigkeit verwenden, mehrfach verwendet wird. Zwei Clients könnten eine identische ID erzeugen. Auch wenn der Server den beiden vertraut, kann es so zu unbeabsichtigten Konflikten kommen. Es reicht also nicht aus, dass der Server nur idempotente Befehle von authentifizierten Clients akzeptiert. Es gilt zusätzlich |
+ | > Damit die Firmen ihre Algorithmen nicht koordinieren müssen, muss ein Teil der Idempotenz-ID die Identität des Absenders enthalten, gemeinsam mit einer Kennung der verwendeten Authentifizierungsmethode. | ||
+ | Angenommen, ich verwende eine simple Benutzername|Passwort Authentifizierung. PRO•M hat für diese Authentifizierungsmethode die Kennung //UN//((für UserName)) vergeben. Ich bin eine Saga, die Aufträge importiert. Die Saga hat ein eigenes Benutzerkonto //OrderImportSagaAccount//, kann sich also mit dem Konto gegen den Server identifizieren. Die Saga kann auf unterschiedlichen Partitionen laufen, läuft immer im Kontext des Kontos, das durch den Benutzername //OrderImportSagaAccount// identifiziert wird. Jede Partition erhält eine eigene Partitions-ID, z.B. //P1//. Nun kann jede Saga auf jeder Partition eindeutige IDs erzeugen, z.B. indem ein einfacher Zähler verwendet wird. Die zusammengesetzte ID ist dann global eindeutig. Angenommen, der Zähler ist bei 5547. Meine eindeutige ID wäre dann | ||
+ | > 5547_P1#OrderImportSagaAccount@UN | ||
+ | Die eindeutige Identifizierung der Saga läuft über das Konto. Der Server analysiert die ID, sieht, dass //UN = Username// zur Identifizierung verwendet werden soll, und prüft, ob das mit dem Typ der mitgesendeten Authentifizierungsdaten übereinstimmt((# und @ sind dabei reservierte Zeichen und dürfen nicht woanders in der ID vorkommen. Wenn doch, wird der Befehl mit einer InvalidIdempotencyIdException abgelehnt)). Dann erkennt der Server, dass der Benutzername //OrderImportSagaAccount// sein soll, und vergleicht das mit dem Inhalt der mitgesendeten Authentifizierungsdaten((nachdem die Daten erfolgreich authentifiziert wurden)). Wenn alles stimmt, wird die ID als global eindeutig akzeptiert. | ||
\\ \\ | \\ \\ | ||
- | Somit muss folgendes passieren: | + | Kein anderes Konto kann einen Befehl mit OrderImportSagaAccount@UN hinter der Raute((#)) senden, bzw. versucht ein anderes Konto das, wird es eine Nichtübereinstimmung mit den gesendeten Authentifizierungsdaten geben, und der Befehl wird mit einer UnauthorizedIdempotencyIdException abgelehnt. Solange die Sagas in den unterschiedlichen Partitionen richtig konfiguriert sind, wird jede ihren eigenen Nummernkreis haben, d.h. die Saga in der P1 Partition kann sich sicher sein, dass niemand anderes _P1#OrderImportSagaAccount@UN verwendet((anstatt P1 kann auch ein Merkmal der Computerhardware verwendet werden, damit man das gar nicht konfigurieren muss)). Die Saga muss somit nur noch für die eigenen Befehle eine eindeutige Kennung erzeugen((Hier reicht ein Zähler)), und wir haben eine globale Eindeutigkeit für die Idempotenz-ID der Befehle. |
- | * Befehl über die ID suchen | + | === Was identifiziert einen Befehl === |
- | * Wenn nicht gefunden, dann verarbeiten, | + | * Von wem stammt der Befehl |
- | * Wenn gefunden, dann alle Aspekte des Befehls prüfen. | + | * Welche Kennung hat der Befehl |
- | * Wenn alles übereinstimmt, dann wurde der Befehl bereits verarbeitet, | + | * Wie lautet der Befehl |
- | * Wenn etwas nicht übereinstimmt, dann eine DuplicateCommandIDException auslösen. | + | * [wann wurde der Befehl gegeben] |
- | Die DuplicateCommandIDException sagt dem aufrufenden Client, dass er eine ID verwendet hat, die bereits von einem **anderen Befehl verwendet wurde**. Wäre es derselbe Befehl gewesen, wäre die Exception nicht ausgelöst worden. Der Client kann somit die Idempotency-ID einfach neu setzen und den Befehl erneut versenden. Das kann völlig gefahrlos geschehen, der Befehl wurde mit Sicherheit noch nicht verarbeitet. | + | In dem oben beschriebenen Mechanismus werden die ersten beiden Punkte berücksichtigt. Wenn man davon ausgeht, dass die Kennung vom Versender eindeutig erzeugt wird, braucht man den Inhalt und Zeitstempel nicht mehr vergleichen. Man könnte das noch machen, und eine DuplicateIdempotencyIdException auslösen wenn eine Idempotenz-ID ankommt, die bereits erhalten wurde, deren Inhalt aber ein anderer war. Das wäre aber übertrieben, bzw. kann auch immer noch dazu gebaut werden. |
- | \\ \\ | + | |
- | Es gibt einen Schwachpunkt. Wenn Befehle über einen Bus versendet werden, würde der Client die DuplicateCommandIDException nicht sofort und synchron erhalten. Es müsste eine entsprechende Nachricht auf dem Bus zurück geschickt werden, damit der Client den Fehler mitbekommt. Das Problem ist natürlich, worauf sich die Nachricht bezieht, die zurück gemeldet wird, da die Idempotency-ID ja nicht zwingend eindeutig ist((darum kümmere ich mich, wenn es soweit ist. Ich halte asynchrone Befehle eh für unnötig)). | + | |
- | \\ \\ | + | |
- | Die Eindeutigkeit eines Befehls wird also nicht über die ID hergestellt, sondern über den gesamten Befehl((Die ID ist ja Teil des Befehls)). Wenn man also z.B. eine Anfrage schicken möchte, ob ein Befehl ausgeführt wurde, schickt man nicht einfach die ID, sondern den kompletten Befehl. | + | |
- | === Beispiel Saga === | + | |
- | Angenommen eine Saga will einen Prozess koordinieren, und erzeugt dazu Befehle. Die Saga kann wie folgt eingerichtet werden, damit sie eindeutige IDs erzeugt((der Inhalt ist dann sogar irrelevant)) - was dann zur Folge hat, dass **auch die mindestens einmalige Ausführung des Befehls garantiert ist**: | + | |
- | * Die Saga läuft im Kontext eines Systemkontos, auf dass nur sie Zugriff hat((bzw. mehrere Sagas/Event Handler, die dann die ID Erzeugung teilen)), | + | |
- | * Der Saga wird eine eigene GloballyUniqueAgentID zugewiesen. Das passiert beim Konfigurieren, und wenn eine Saga auf unterschiedlichen Partitionen läuft, erhält sie pro Partition eine eigene ID((die ID kann ein einfacher Zahlenwert sein, eine GUID, oder etwas eigenes, z.B. "OrderFullfillingSaga74", hauptsache sie wird zentral für alle PRO•M/System Agenten vergeben)), | + | |
- | * Die Saga erzeugt zu ihrer GloballyUniqueAgentID eine AgentUniqueCommandID. Sie kann hierfür alle vorherigen IDs merken, oder einfach einen Zähler verwenden((dann muss sich die AgentUniqueCommandID Vergabe lediglich eine Zahl merken, und nicht alle bisher verwendeten)), | + | |
- | * Die CommandID ist dann die GloballyUniqueAgentID + "[was auch immer die Saga hier einfügen möchte, entweder gar nichts oder z.B. einen Untertrich] + CommandID, also z.B. "OrderFullfillingSaga74_2447". | + | |
- | Die Saga hat die Sicherheit, dass die ID eindeutig ist. Alle anderen Agents haben eigene GloballyUniqueAgentIDs. Wenn jemand von Außen einen Agent baut, wird er nicht die Anmeldedaten des Systemkontos haben, dass die Saga verwendet((und wenn doch, muss die Vergabe der GloballyUniqueAgentID mit der Saga koordiniert werden)). Auch wenn dieser Agent zufällig dieselbe Befehls-ID erzeugt wie die Saga, ist es trotzdem ein anderer Befehl, weil ein anderes Konto verwendet wurde um ihn zu versenden! Genauso können keine Endanwender in einen Konflikt mit der Befehls-ID geraten, weil sie kein Systemkonto, sondern Benutzerkonten verwenden, um Befehle abzuschicken. | + | |
=== Beispiel Endanwender === | === Beispiel Endanwender === | ||
- | Wenn ein Endanwender einen Befehl absendet, braucht es i.d.R. gar keine CommandID. Wenn man aber z.B. einbaut, dass sich die Client Anwendung die gesendeten Befehle merkt, damit nach einem unerwarteten Fehler und einem Neustart geprüft werden kann, was mit dem Befehl passiert ist((erhalten, ausgeführt?)), kann der Client eine Befehls-ID schreiben. Damit das einfach bleibt, sollte der Client einfach eine GUID verwenden. Eine GUID, zusammen mit einem Zeitstempel und der Identität des angemeldeten Users, sowie dem gesamten Inhalt des Befehls ist mehr als ausreichende Sicherheit, dass der Befehl eindeutig ist. | + | Wenn ein Endanwender einen Befehl absendet, braucht es i.d.R. gar keine Idempotenz-ID. Wenn man aber z.B. einbaut, dass sich die Client Anwendung die gesendeten Befehle merkt, damit nach einem unerwarteten Fehler und einem Neustart geprüft werden kann, was mit dem Befehl passiert ist((erhalten, ausgeführt?)), kann der Client eine Befehls-ID schreiben. Damit das einfach bleibt, sollte der Client einfach eine GUID verwenden, vielleicht gemeinsam mit einem Zeitstempel((und wie für jede ID gefordert mit der Identität des angemeldeten Users)). |
- | > //Mehr als ausreichend?// Für Systemagenten((Sagas, Event Handler)) ist die Sicherheit vollständig, solange das Konto nicht gehackt wird. Wieso nicht für Benutzer? Nun, eine GUID gilt in anderen System als ausreichend. Ich verwende //zusätzlich// einen Zeitstempel, das Konto des Absenders und den Inhalt des Befehls((der u.a. die AggregateID enthält, plus eventuell die Version, d.h. der Befehl ist an genau eine Version eines Objektes gerichtet)). Das ist so sicher wie es nur möglich ist. | + | |
- | Es gibt soviele Möglichkeiten die Eindeutigkeit der ID der Befehle von Endanwendern eindeutig zu machen. Man kann eindeutige Sitzungs-IDs verwenden, die IDs vom Endpunkt erzeugen lassen, an den der Client ihn letztendlich sendet, usw. Alle diese Möglichkeiten sind recht komplex, weil die Client Anwendungen nicht kontrolliert eingerichtet und konfigurert werden, sondern von jedem Anwender beliebig oft und an beliebiger Stelle installiert werden kann. | + | |
\\ \\ | \\ \\ | ||
- | Wozu also dieser Umstand für Endanwender? So, wie es gebaut ist, ist es doch bereits sicherer als sich morgens die Zähne zu putzen - die Wahrscheinlichkeit, dass man bei Zähneputzen von einem Meteoriten getroffen wird ist deutlich größer als die Möglichkeit, dass ein Befehl identisch erzeugt wird. Und wenn doch? Dann ist ein Meteoriteneinschlag schlimm. Ein nicht ausgeführter Befehl aber nicht. Der Anwender führt die Aktion einfach nochmal durch wenn das Ergebnis nicht zu sehen ist. Kann ja nichts passieren, das System stellt ja sicher, dass es (schließlich) konsistent ist, und das weil | + | Für Systemagenten((Sagas, Event Handler)) ist die Eindeutigkeit Pflicht, weil die schließliche Konsistenz und auch die Synchronisierung mit anderen Systemen davon abhängt. Die Sicherheit ist vollständig gegeben, solange das Konto nicht gehackt wird, das von den Systemagenten verwendet wird. Für einen Endanwender wird es i.d.R. gar nicht notwendig sein, eine eindeutige ID zu setzen. Wenn doch, gibt es auch noch andere Methoden, das sicher zu machen, z.B. indem man dem Konto TANs gibt, und der Anwender für idempotente Befehle die TANs verwendet. |
- | > die Befehlseindeutigkeit für System Agenten sicher gestellt werden kann, was die (schließliche) Konsistenz des Systems garantiert. | + | |
- | Das war mir wichtig. Hier dürfen keine Fehler passieren. Der Anwender kann Fehler immer selbst korrigieren. | + | |
== Impersonation == | == Impersonation == | ||
Eine Saga führt u.U. einen Prozess fort, der durch einen Endanwender angestoßen wurde. Damit der Prozess im Rechtekontext des Endanwenders läuft, kann die Saga den Endanwender //impersonieren//, also im Rechtekontext des (Benutzer)Kontos des Endanwenders laufen, obwohl sie sich mit ihrem Systemkonto authentifiziert. Das Systemkonto der Saga muss dafür eine Impersonationsrecht auf das Benutzerkonto des Endanwenders erhalten. | Eine Saga führt u.U. einen Prozess fort, der durch einen Endanwender angestoßen wurde. Damit der Prozess im Rechtekontext des Endanwenders läuft, kann die Saga den Endanwender //impersonieren//, also im Rechtekontext des (Benutzer)Kontos des Endanwenders laufen, obwohl sie sich mit ihrem Systemkonto authentifiziert. Das Systemkonto der Saga muss dafür eine Impersonationsrecht auf das Benutzerkonto des Endanwenders erhalten. | ||
Line 60: | Line 45: | ||
* Die Autorisierung verwendet das Konto des impersonierten Kontos, | * Die Autorisierung verwendet das Konto des impersonierten Kontos, | ||
* Die Feststellung der Befehlseindeutigkeit verwendet das Konto des impersonierenden Kontos((oder beiden, aber das impersonierende erzeugt mit Sicherheit eindeutige IDs, also reicht das)). Das passt auch ins Bild, denn der Befehl //kommt von// dem Konto des impersonierenden Kontos, er wird dann akzeptiert((autorisiert)) nachdem festgestellt wurde, dass das impersonierende Konto die Aktion im Namen des impersonierten Kontos ausführen darf. | * Die Feststellung der Befehlseindeutigkeit verwendet das Konto des impersonierenden Kontos((oder beiden, aber das impersonierende erzeugt mit Sicherheit eindeutige IDs, also reicht das)). Das passt auch ins Bild, denn der Befehl //kommt von// dem Konto des impersonierenden Kontos, er wird dann akzeptiert((autorisiert)) nachdem festgestellt wurde, dass das impersonierende Konto die Aktion im Namen des impersonierten Kontos ausführen darf. | ||
+ | == Umstellungen im laufenden Betrieb == | ||
+ | Wenn die Authentfizierungsmethode umgestellt wird, und eine Saga bereits einen Befehl mit der ID 5547_P1#OrderImportSagaAccount@UN versendet hat, aber nicht weiß, ob er angekommen ist, würde sie ihn nochmal absenden. Damit das so reibungslos wie nur möglich klappt, muss folgendes passieren: | ||
+ | * Der Server prüft nach der Authentifizierung immer zuallererst, ob die ID bereits verarbeitet wurde. Nur für den Fall, dass sie es nicht wurde, wird geprüft, ob die ID ein korrektes Format hat und auch dem authentifizierten Konto zugeordnet werden kann. Wenn nicht, weil z.B. nicht mehr die Methode //UN// sondern //Certificate// verwendet wird, wird eine IdempotencyIdAuthorizationException ausgelöst. | ||
+ | * Der Sender erzeugt nach dem Erhalt einer IdempotencyIdAuthorizationException einfach eine neue ID, bzw. prüft erst, ob der Teil, der ihn authentifiziert((OrderImportSagaAccount@UN)) noch aktuell ist. Er weiß dann nämlich, dass es nur an solch einer Umstellung gelegen haben kann. | ||
+ | Mit diesen einfachen Regeln wird die Saga ihre Arbeit immer fortsetzen können. |