Die Denormalisierer, die aus den Ereignissen, die vom Event Sourcing geliefert werden, die Projektionen bauen, greifen veröffentlichte und gespeicherte Ereignisse ab, und erstellen daraus Sichten die effizient abzufragen sind. Es kann beliebig viele solcher Denormalisierer geben. Manch komplexe Liste kann aufwendig zu bauen sein. Damit das Speichern von Ereignissen am Ende eines Befehlsaufrufs nicht durch die Denormalisierer verlangsamt wird, werden erst die Ereignisse gespeichert, und dann in einem anderen Prozess1) von den Event Handlern denormalisiert.
Das bedeutet nun, dass die denormalisierten Listen nicht transaktional mit den Ereignissen gespeichert werden. Wenn ich die Adresse einer Person ändere, und die Aktion erfolgreich durchgeführt wurde, kann es sein, dass die Adressliste, die ich gleich danach aufrufe, noch die alte Adresse anzeigt, weil die neue noch nicht vom Event Handler behandelt wurde. Die Änderung wird schlussendlich in der Liste landen, weil sie ja passiert ist, nur ist nicht klar, wie lange das dauert2). Diese Tatsache gibt eventual consistency3) seinen Namen.
Ich bespreche hier kurz den Unterschied zu der herkömmlichen Methode, wie ich es bisher gemacht habe.
Das System kann für eventual consistency gebaut werden, und eventual consistency kann durchaus als Werkzeug gesehen werden um das System verfügbar und reagierend zu halten. Angenommen ein Prozess beinhaltet diverse Schritte4). Eine Saga, die den Prozess steuert, kann alle Befehle abschicken, ohne auf das Ergebnis jedes Befehls zu warten. Sie wartet auf Ereignisse, die irgendwann als Ergebnis der Befehle zurück kommen, kann das aber asynchron machen. So kann die Saga den Prozess optimiert gestalten, und nur die Schritte seriell durchführen, die serialisiert werden müssen.
Wenn die Saga einen Befehl abschickt, erhält sie mindestens eine Empfangsbestätigung5) - wenn der Befehl synchron ausgeführt wird, kann das Ergebnis auch mehr als nur ein ACK beinhalten. Was ist mit unerwarteten Fehlern? Im einfachsten Fall bricht die Saga den Prozess ab sobald etwas unerwartetes passiert. In bestimmten Fällen kann sie den Befehl aber einfach erneut senden, so lange6) und so oft, bis sie ein ACK erhält. Wenn der Befehl in sich idempotent ist, kann die Saga ihn ohne Folgen mehrmals absenden. Ist er nicht idempotent, muss er es gemacht werden. Die Saga muss diese Idempotenz dann speichern bevor sie den Befehl das erste mal absendet, um die eventuell folgenden Befehle dann mit derselben Idempotenz zu versenden.
Die Saga wartet dann nach dem ACK auf Ereignisse, aus denen die Saga schließen kann, was das System mit dem Befehl gemacht hat7). Sie kann dann den Prozess entweder weiter führen oder abbrechen. Wenn die Saga nach einer definierten Zeit keine Ereignisse erhält, kann sie anfangen den Prozess rückgängig zu machen, zu kompensieren.
Der Anwender ist wie eine Saga ein Akteur im System. Er könnte genau wie die Saga agieren:
Der Anwender muss somit dahingehend erzogen werden, in dem System korrekt zu agieren. Dazu muss er wissen, was es mit eventual consistency auf sich hat, und wie das System auf Befehle reagiert. Ein ungeschulter Anwender würde die Erwartung haben, dass alle Datensichten alle Aktionen berücksichtigen, die von ihm ausgelöst wurden. Mit eventual consistency muss das nicht der Fall sein.
Das kann ziemlich böse Auswirkungen haben, vor allem wenn der Anwender das anders gewohnt ist. Es kann dazu führen, dass der Anwender dem System nicht vertraut, die Änderung wieder und wieder angibt10), usw. Davon auszugehen, dass alle Anwender erziehbar sind, halte ich für kritisch. Das sollte so gelöst werden, dass der Anwender so wenig wie möglich mit denken muss, ansonsten wird es immer wieder Anwender geben, die das System fehlerhaft bedienen11). Einige Möglichkeiten der Optimierung:
Die letzten beiden Punkte sind für sich selbst nicht sehr aussagekräftig. Der Zeitstempel bedeutet ja nicht, dass alle Befehle bis 19:00 abgearbeitet wurden, nur, dass der Denormalisierer alle Ereignisse, die bis 19:00 veröffentlicht wurden, berücksichtgigt. Wenn ich als Anwender also einen Befehl um 18:59:59 gesendet habe, und die Sicht einen Zeitstempel von 19:00 hat, weiß ich nicht, ob der Befehl Teil der Sicht ist oder nicht. Ich weiß es sogar mit einem Zeitstempel von 20:00 oder 23:00 nicht mit Sicherheit! Ob und wie so ein Zeitstempel also umgesetzt wird muss also noch geklärt werden.
Nach dem SRP16) sollte eine Klasse genau eine Aufgabe haben. Abgeleitet davon wird es oft so definiert, dass es nur genau einen Grund geben sollte, warum man die Klasse ändern müsste. To be continues…