Pollen ist das regelmäßige Anfragen nach Daten, so lange, bis das gewünschte Ergebnis da ist oder man sich entscheidet, das Pollen abzubrechen. Pollen ist im Zusammenhang mit Eventual Consistency sehr nützlich. Wenn ich einen Befehl abschicke, weiß ich auch nach einem ACK1) nicht, ob das Ergebnis in den Sichten, die ich mir angucken kann, bereits vorhanden ist.
Wenn mir somit der Empfang oder die Durchführung eines Befehls bestätigt wird, habe ich im Grunde lediglich die Möglichkeit danach zu pollen, ob das Ergebnis auch schon in den Sichten angekommen ist, bevor ich mir die Sichten angucke. Die Frage ist natürlich, wonach ich polle.
Wenn ich einen neuen Datensatz mit einer neuen Aggregate ID angelegt habe, und mir der Befehl2) bestätigt wurde, kann ich nach der Aggregate ID pollen, d.h. bevor ich mir eine Mitarbeiterliste angucke, kann ich solange fragen, ob der neue Mitarbeiter schon in die Liste mit eingearbeitet wurde, bis ich eine positive Antwort erhalte, bevor ich mir dann die Liste ziehe.
Der neue Mitarbeiter, bzw. die ID des neuen Mitarbeiters, ist in diesem Fall ein natürliches Polling-Kennzeichen. Es ist zwar theoretisch möglich, dass ich nach der ID frage, der Mitarbeiter noch nicht in der Liste ist. Jetzt sieht jemand anderes den Mitarbeiter, löscht ihn wieder, und die Neuanlage und das Löschen werden beide von der Liste berücksichtigt, bevor ich das nächste mal nach der ID frage. Die ID ist immer noch nicht da, aber nicht weil sie nicht angekommen ist, sondern weil sie bereits wieder gelöscht wurde. Da kann ich lange Pollen.
Diesen Fall halte ich aber nicht für dramatisch. Das Polling bricht immer nach einer definierten Zeit ab, z.B. 20 Sekunden. Wenn ich dann mitbekomme, das jemand anderes den Mitarbeiter gelöscht hat, akzeptiere ich auch das Verhalten vom System.
Was aber, wenn ich was am Mitarbeiter ändere? Dann kann ich nicht nach einer eindeutigen ID pollen. Ich brauche also was anderes:
Als PollingID reicht eine GUID völlig aus3). Wenn ich nach etwas pollen möchte, setzte ich die PollingID einfach mit Guid.NewGuid()4). Das Ganze wird noch durch folgende Mechanismen sicherer gemacht:
> Dadurch, dass die PollingID Benutzerkonto abhängig gemacht wird, kann es nicht passieren, dass sich zwei Benutzer gegenseitig in die Quere kommen.
Über die PollingID werden immer nur bestimmte Sichten abgefragt, also auch wenn ein Anwender dieselbe PollingID für zwei Befehle verwenden sollte, ist das nur schlimm8) wenn die Befehle auch dieselbe Sicht betreffen.
Die PollingID ist ein String. Der Client könnte auch eine GUID+AggregateID+Version+Zeitstempel setzen, um das noch sicherer zu machen.
Auch wenn es einen Konflikt gibt, bedeutet das im schlimmsten Fall, dass das System sagt, dass ein Ergebnis sichtbar ist, obwohl es noch nicht sichtbar ist. Das wäre zwar keine besonders vertrauensbildende Benutzererfahrung, aber ja auch nicht weiter schlimm.
Die PollingID kann u.U. recht wichtig werden. Ein Anwender führt ja Prozessschritte durch, ähnlich wie ein Systemagent12), und verwendet die gepollten Ergebnisse für die Entscheidung, welche Schritte als nächstes durchzuführen sind. Im besten Fall passiert folgendes:
Eine der großen Gefahren besteht dann, wenn Sichten implizit verwendet werden um Schritte außerhalb des Systems durchzuführen. Ein Beispiel:
In PRO•M erfasst ein Mitarbeiter seine Zeiten, und druckt dann irgendwann seinen Tätigkeitsbericht aus. Dieser wird dem Kunden zur Unterschrift vorgelegt - das ist der Prozessschritt außerhalb des Systems. Die erfassten Zeiten des Mitarbeiters liegen dann einer späteren Rechnungsstellung zugrunde.
Der Mitarbeiter druckt den Bericht nachdem er alle seine Zeiten erfasst hat. Einige der Zeiten13) sind aber noch gar nicht in die Sicht denormalisiert worden. Der Kunde unterschreibt somit für 8 geleistete Stunden. Die Rechnung wird dann mit allen Zeiten erstellt, also mit allen 12 Stunden. Der Kunde hat aber nie für diese 12 Stunden unterschrieben14).
Man könnte diesen Fall lösen, indem man einige der Schritte implizit macht. Das System wäre dann zumindest schließlich konsistent. Z.B. müsste der Mitarbeiter den Tätigkeitsbericht, den er dem Kunden zur Unterschrift vorlegt, explizit erstellen. Wenn der Bericht nur 8 von 12 geleisteten Stunden enthält, dann wird das irgendwann später erkannt, weil 4 Stunden noch in keinem Bericht stehen. Diese können dem Kunden dann nachträglich vorgelegt werden.
Rechnungen werden auch nur auf erstellte Tätigkeitsberichte gestellt, nicht einfach auf erfasst Zeiten. Wenn nun ein neuer Tätigkeitsbericht mit den 4 noch nicht abgerechneten Zeiten erstellt wird, kann dieser abgerechnet werden.
So ist das System (schließlich) konsistent. Es würde lediglich inakzeptabel sein, wenn die Zeiten viel später als erwartet sichtbar sind, und vielleicht Tage nach Rechnungsstellung auftauchen.
Das System ist aktuell so gebaut, dass die Sichten sofort konsistent sind, nicht schließlich konsistent, weil sie in derselben Transaktion gebaut werden wie auch die Ausführung des Befehls. Entweder muss das so bleiben, man muss den Workflow ändern, oder man macht folgendes:
Jede Sicht wird aus Ereignissen aus einem oder mehrerer Event Stores gebaut. Die Ereignisse in einem Event Store enthalten die Ergebnisse von bisher ausgeführten Befehlen. Ich als Anwender hätte gerne die Sicherheit, dass ich für eine gewisse Aktion, die ich am dd.mm.yyyy um hh:nn:ss starte, auch die Ereignisse aller Befehle, die bis dahin ausgeführt wurden, zur Verfügung habe. Wie kann ich das machen, ohne mich auf koordinierte Uhrzeiten auf den verschiedenen Systemen zu verlassen?
Event Stores müssen zwei Dinge zusichern