User Tools

Site Tools


technology:polling

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

technology:polling [2013/01/17 13:28]
rtavassoli
technology:polling [2013/01/18 19:04] (current)
rtavassoli [Der Tätigkeitsbericht in PRO•M]
Line 3: Line 3:
 \\ \\ \\ \\
 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 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.
 +===== Pollen nach erwarteten Ergebnissen =====
 +Wenn ich einen neuen Datensatz mit einer neuen Aggregate ID angelegt habe, und mir der Befehl((Empfang, bzw. Ausführung)) 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.
 \\ \\ \\ \\
-Die Lösung wird ähnlich sein wie die für die [[concepts:idempotency|Idempotenz ID]]. Ich als Absender des Befehls((Command)) setze im Befehl eine Polling IDDiese Polling ID wird in alle Ereignisse((Domain Events)) geschriebendie ein direktes Ergebnis dieses Befehls sind. Event Handlers und Sagas, die Ereignisse abfangen und als Reaktion auf diese neue Befehle absendenübernehmen die Polling ID in die Befehle. Die Event Handler und Sagas haben keine eigene Verwendung für die Polling ID((Die Saga setzt eine Correlation ID, die anders ist, weil eine Saga von einem Ereignis ausgelöst wird und die Correlation ID einfach [Aggregate ID, Version, Saga Typ] istWenn ein Anwender einen Befehl sendet, wird die Version nicht zwingend mit geschickt, und der Typ des Clients ist nicht eindeutig/singleton wie die Saga)).+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ücksichtigtbevor ich das nächste mal nach der ID frage. Die ID ist immer noch nicht daaber nicht weil sie nicht angekommen ist, sondern weil sie bereits wieder gelöscht wurdeDa kann ich lange Pollen.
 \\ \\ \\ \\
-Die Polling ID ist recht wichtig. Wenn ich z.B. einen Report ausdrucken möchte, der alles, was ich erfasst und angegeben habebeinhalten solldann möchte ich das erst machen nachdem alle Angaben auch im Report enthalten sind. Das Pollen kann aber erst gestartet werden, nachdem der Befehl bestätigt wurde. Bei unerwarteten Fehlern beim Bestätigen sollte der Befehl erneut geschickt werden, so lange, bis mir entweder mitgeteilt wurde, dass er fehlgeschlagen ist, oder dass er durchgeführt wurde. Dafür wird die [[concepts:idempotency|Befehlsidempotenz]] verwendet. Erst danach kann das Pollen starten.+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 mitbekommedas jemand anderes den Mitarbeiter gelöscht hatakzeptiere ich auch das Verhalten vom System.
 \\ \\ \\ \\
-Da ich erst pollenachdem ich weißdass der Befehl bestätigt wurdeweiß ich, dass ich schließlich((eventually)) ein Ergebnis mit der Polling ID erhalten werdeIch könnte also unendlich lang pollen. Wenn es zu lange dauert, sollte der Admin informiert werden, dass Ereignisse irgendwo in einer Pipeline hängen geblieben sindDie Frage ist, woraus sich die Polling ID zusammen setzt+Was aberwenn ich was am Mitarbeiter ändere? Dann kann ich nicht nach einer eindeutigen ID pollen. Ich brauche also was anderes: 
-===== Polling ID ===== +===== Pollen ohne natürliches Polling-Kennzeichen ===== 
-Die PollingID ist die [[concepts:idempotency|idempotente]] und somit auch global eindeutige Befehls-IDWenn ein Befehl ohne PollingID ausgeführt wirdwird im Domain Event automatisch die BefehlsID als PollingID gesetztWenn ein Event Handler nun ein Ereignis denormalisiertkann er die PollingID in die Liste der behandelten IDs aufnehmen, damit er, wenn danach gefragt wird, antworten kann, dass die PollingID in der denormalisierten Menge enthalten ist.+Als PollingID reicht eine GUID völlig aus((wobei ein String-Wert genommen wirddamit der Client sich entscheiden kannvielleicht doch noch zusätzliche Dinge zu setzen, wie z.B. einen Zeitstempel)). Wenn ich nach etwas pollen möchtesetzte ich die PollingID einfach mit Guid.NewGuid()((vielleicht noch + einen Zeitstempel)). Das Ganze wird noch durch folgende Mechanismen sicherer gemacht: 
 +  - Wenn ein Denormalisierer ein Ereignis zum Denormalisieren empfängt, das eine PollingID enthält, speichert er in der Transaktion, in der er denormalisiert, auch diese PollingID, plus die AccountID des authentifizierten Kontos in einer Polling TabelleDazu speichert er den Zeitstempel des Beginns der Denormalisierung. Gibt es für die AccountID/PollingiD Kombination bereits einen Eintrag, wird der Zeitstempel angepasst, 
 +  - Der Denormalisierer stellt sicher, dass die PollingID von dem Anwender((durch die AccountID identifizierbar)) eine bestimmte Zeit lang zur Verfügung steht. Da ein Client immer nur eine gewisse Zeit lang pollt, muss die garantierte Verfügbarkeitszeit lediglich mindestens so lang sein, wie die längste Polling Dauer des Clients. Ich denke, eine Stunde sollte völlig reichen. 
 +  - Wird eine Sicht jetzt nach einer PollingID gepollt, wird einfach ein SELECT Timestamp FROM MyPollingTable WHERE AccountID = @AccountID AND PollingID = @PollingID ausgeführt. Wenn das Ergebnis ein Ergebnis liefert mit Timestamp > Jetzt abzgl. Verfügbarkeitsgarantie, wird ein positives Ergebnis gemeldet, ansonsten ein negatives((wenn der Timestamp länger als eine Stunde her ist, gehe ich einfach mal davon aus, dass das jetzt eine neue PollingID sein soll)). 
 +  - Ein zweiter Prozess((oder Thread)) räumt die alten PollingIDs auf, die älter als die garantierte Verfügbarkeitszeit sind. 
 +> Dadurch, dass die PollingID Benutzerkonto abhängig gemacht wird, kann es nicht passierendass 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 schlimm((falsche positive Antwort auf eine Pollinganfrage)) 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. 
 + 
 +> Da eine PollingID nach einer bestimmten Zeit verfällt((sollte man dann vielleicht doch für Befehle einführen - wobei Event Handler Befehle auch ausführen können müssen, nachdem das System vielleicht 2 Tage nicht verfügbar war, also da lieber händisch aufräumen, sollten die Befehls-IDs zu viele werden)), ist die Wahrscheinlichkeit, dass in diesem Zeitraum((eine Stunde)) von einem Client((i.d.R. ist eine Anwender auch nur mit einem Client auf einem Computer angemeldet)) dieselbe PollingID für dieselbe Sicht erzeugt wird im Grunde nullweil die GUID Generierung auf einem Rechner eindeutig sein muss! 
 + 
 +> 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
 +===== Lauernde Gefahren ===== 
 +Die PollingID kann u.U. recht wichtig werden. Ein Anwender führt ja Prozessschritte durch, ähnlich wie ein Systemagent((Saga))und verwendet die gepollten Ergebnisse für die Entscheidung, welche Schritte als nächstes durchzuführen sindIm besten Fall passiert folgendes: 
 +  - Anwender schickt Befehl ab, 
 +  - Anwender pollt nach Ergebnis, 
 +  - Ergebnis steht innerhalb des Pollingzeitraums zur Verfügung, 
 +  - Anwender lädt Ergebnis, 
 +  - Anwender trifft Entscheidung über nächste Schritte. 
 +Eine der großen Gefahren besteht dann, wenn Sichten implizit verwendet werden um Schritte außerhalb des Systems durchzuführen. Ein Beispiel: 
 +==== Der Tätigkeitsbericht in PRO•M ==== 
 +In PRO•M erfasst ein Mitarbeiter seine Zeitenund 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. 
 +=== Die Gefahr === 
 +Der Mitarbeiter druckt den Bericht nachdem er alle seine Zeiten erfasst hat. Einige der Zeiten((z.B. 4 geleistete Stunden)) 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 erstelltalso mit allen 12 Stunden. Der Kunde hat aber nie für diese 12 Stunden unterschrieben((oder die Rechnung rechnet nur 6 Stunden ab, weil dort noch weniger der geleisteten Stunden angekommen sind)).
 \\ \\ \\ \\
-Es kann aber seindass ein Befehl einen Prozess auslöstund dass Event Handler und Sagas weitere Befehle absendenum diesen Prozess zu beendenDiese Event Handler und Sagas können nun die PollingID zusammen mit der Befehls-ID speichernund wenn sie gepollt werdengeben sie die Befehls-ID((die neue Polling-ID)) raus.+Man könnte diesen Fall lösenindem man einige der Schritte implizit macht. Das System wäre dann zumindest //schließlich konsistent//. Z.B. müsste der Mitarbeiter den Tätigkeitsberichtden er dem Kunden zur Unterschrift vorlegtexplizit erstellenWenn der Bericht nur 8 von 12 geleisteten Stunden enthältdann wird das irgendwann später erkanntweil 4 Stunden noch in keinem Bericht stehen. Diese können dem Kunden dann nachträglich vorgelegt werden.
 \\ \\ \\ \\
-Alternativ könnten die Event-Handler/Sagas die Polling-ID im Befehl direkt setzen, und zwar mit der Polling-ID des auslösenden EreignissesAuch nicht uninteressantDamit Polling-IDs global eindeutig bleiben, sollte das aber nur bestimmten System Agenten((siehe Diskussion in der Diskussion der [[concepts:idempotency|Idempotenz]]))erlaubt werden, damit sie keine wilkürlichen Polling-IDs setzen.+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 BefehlsEntweder muss das so bleiben, man muss den Workflow ändern, oder man macht folgendes: 
 +=== Warten auf Aktualität der Sicht === 
 +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 
 +  - man kann sie nach der aktuell höchsten Ereignis ID((nicht nach der Sequenz, die kann auch noch NULL sein)) fragen. Diese ist pro Event Store eindeutig. 
 +  - ...to be continued
technology/polling.1358425736.txt.gz · Last modified: 2013/01/17 13:28 by rtavassoli