User Tools

Site Tools


technology:domainmodel:command:commanduniqueness

This is an old revision of the document!


Befehlseindeutigkeit

In einem schwach zusammenhängenden System1), 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ßlich2) 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.

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 Prozess3) 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 haben4). Event Handler sollten also bei nicht idempotenten Ereignisinhalten auf die ID/Version prüfen5).

Befehle sind nicht auf natürliche Art idempotent6). 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.

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 Handler7) 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.

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 Prozesse8) 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 eindeutig macht.

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.

Ich meine, man kann komplexere Lösungen bauen, die kaum etwas zusätzliches kosten, das System aber noch robuster machen9). 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.

Wenn die ID auch noch frei konfigurierbar ist10), dann sind alle Systemanforderungen für eine fast lückenlose Sicherheit gegeben11).

Was identifiziert einen Befehl

Der Befehl wird identifiziert über

  • das authentifizierte Konto12), also von wem stammt der Befehl?
  • den Zeitstempel, also wann wurde der Befehl gegeben,
  • 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 gibt13). Gibt es sie bereits14), 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ührt15).

Gibt es die ID16) 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.

Somit muss folgendes passieren:

  • Befehl über die ID suchen
  • Wenn nicht gefunden, dann verarbeiten,
  • Wenn gefunden, dann alle Aspekte des Befehls prüfen.
    • Wenn alles übereinstimmt, dann wurde der Befehl bereits verarbeitet,
    • Wenn etwas nicht übereinstimmt, dann eine DuplicateCommandIDException auslösen.

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.

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 ist17).

Die Eindeutigkeit eines Befehls wird also nicht über die ID hergestellt, sondern über den gesamten Befehl18). 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 erzeugt19) - 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 hat20),
  • 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 ID21),
  • Die Saga erzeugt zu ihrer GloballyUniqueAgentID eine AgentUniqueCommandID. Sie kann hierfür alle vorherigen IDs merken, oder einfach einen Zähler verwenden22),
  • 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 verwendet23). 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

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

Mehr als ausreichend? Für Systemagenten25) 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 Befehls26). 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

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

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.

Für die Befehlseindeutigkeit heißt das, dass das Konto, das zur Herstellung der Identität des Befehls verwendet wird, nicht das Konto des Endanwenders ist, sondern das Konto der Saga. Ansonsten wäre ja nicht mehr sicher gestellt, dass dieser Befehlt eindeutig ist, weil nur die Systemkonten Mechanismen für eine absolute Sicherheit für eindeutige Befehls IDs bieten. Bei Impersonierten Konten heißt das also

  • Die Autorisierung verwendet das Konto des impersonierten Kontos,
  • Die Feststellung der Befehlseindeutigkeit verwendet das Konto des impersonierenden Kontos27). Das passt auch ins Bild, denn der Befehl kommt von dem Konto des impersonierenden Kontos, er wird dann akzeptiert28) nachdem festgestellt wurde, dass das impersonierende Konto die Aktion im Namen des impersonierten Kontos ausführen darf.
1) loosely coupled system
2) eventually
3) Saga oder UI Client
4) und einen Ereignistyp, der den Typ des Aggregates eindeutig bestimmt
5) oder auf die Publisher Sequenz, die einfach pro Veröffentlicher eine laufende Nummer ist
6) außer möglicherweise die Auswirkung des Befehls
7) der den Befehl letzten Endes ausführt
8) die von Sagas durchgeführt werden
9) Murphys Gesetz sagt ja, dass, wenn etwas schief läuft, es so richtig schief läuft - frei gedeutet
10) ein ausreichend großer String
11) vorausgesetzt, dass System wird nicht gehackt
12) d.h. dass Befehle auch nur vom Command Handler gespeichert und danach ausgeführt werden, wenn sie authentifiziert wurden
13) d.h. die ID reicht als lookup/SELECT Element
14) vielleicht mehr als einmal
15) 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
16) Guid
17) darum kümmere ich mich, wenn es soweit ist. Ich halte asynchrone Befehle eh für unnötig
18) Die ID ist ja Teil des Befehls
19) der Inhalt ist dann sogar irrelevant
20) bzw. mehrere Sagas/Event Handler, die dann die ID Erzeugung teilen
21) 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
22) dann muss sich die AgentUniqueCommandID Vergabe lediglich eine Zahl merken, und nicht alle bisher verwendeten
23) und wenn doch, muss die Vergabe der GloballyUniqueAgentID mit der Saga koordiniert werden
24) erhalten, ausgeführt?
25) Sagas, Event Handler
26) der u.a. die AggregateID enthält, plus eventuell die Version, d.h. der Befehl ist an genau eine Version eines Objektes gerichtet
27) oder beiden, aber das impersonierende erzeugt mit Sicherheit eindeutige IDs, also reicht das
28) autorisiert
technology/domainmodel/command/commanduniqueness.1360096354.txt.gz · Last modified: 2013/02/05 21:32 by rtavassoli