This shows you the differences between two versions of the page.
technology:domainmodel:secondaryindex [2013/01/29 16:10] rtavassoli [Warum Sekundärindizes wie kompensierende Aktionen für eine schließliche Konsistenz funktionieren] |
technology:domainmodel:secondaryindex [2013/02/18 18:23] (current) rtavassoli |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Sekundärindex - Secondary Index ====== | + | ====== Mengenindex - Set Index ====== |
- | In DDD heißt es, es gäbe keine secondary indices. Ich behaupte, dass die explizite Verwendung von Zweitindizes eine pragmatische Abweichung von der reinen Anwendung von DDD ist. Durch Zweitindizes kann man den komplizierten Einsatz von Domain Services, SAGAs, Reservation Patterns und Compensating Actions umgehen. Man sollte damit natürlich vorsichtig sein, wobei der übermäßige Einsatz auch nicht schadet, weil ein Zweitindex immer relativ einfach eben durch einen anderen Prozess ersetzt werden kann, der Eventual Consistency verwendet, bzw. sofortige Konsistenz mit dem Reservation Pattern. | + | In DDD heißt es, es gäbe keine transaktional einzuhaltenden, Mengenbasierten Regeln, die mehrere Aggregate umfassen. Ich behaupte, dass die explizite Verwendung von Mengenindizes eine pragmatische Abweichung von der reinen Anwendung von DDD ist. Ein Mengenindex ist im Grunde eine IoC((Inversion of Control)) von Entitäten und Aggregates, indem aus Entitäten Aggregates werden, und die Aggregates auf den Mengenindex((das ursprüngliche Aggregate)) zeigen. Durch Mengenindizes kann man den komplizierten Einsatz von Domain Services, SAGAs, Reservation Patterns und Compensating Actions umgehen. Vor allem aber kann man dieselbe Funktionalität von größeren Aggregates haben, und die Aggregates trotzdem klein halten. Man sollte damit natürlich vorsichtig sein, wobei der übermäßige Einsatz auch nicht schadet, weil ein Mengenindex immer relativ einfach eben durch einen anderen Prozess ersetzt werden kann, der Eventual Consistency verwendet, bzw. sofortige Konsistenz mit dem Reservation Pattern. |
\\ \\ | \\ \\ | ||
- | Wenn man sich relativ sicher sein kann, dass ein Aggregte immer gemeinsam mit dem Zweitindex in derselben Partition liegen wird, dann sollte der Einsatz eines Zweit- oder auch Drittindexes auch eine langfristige Lösung darstellen. Die Umsetzung ist relativ einfach - ein paar technische Voraussetzungen müssen eingebaut werden. Dann können Zweitindizes sogar so gebaut werden, dass sie sehr gut in DDD passen, denn dann sind sie nichts anderes als weitere Zweit- und Dritt-Aggregates, die die Ereignisse von anderen Aggregates verwenden. | + | Wenn man sich relativ sicher sein kann, dass ein Aggregte immer gemeinsam mit dem Mengenindex in derselben Partition liegen wird, dann sollte der Einsatz eines Mengenindexes auch eine langfristige Lösung darstellen. Die Umsetzung ist relativ einfach - ein paar technische Voraussetzungen müssen eingebaut werden. Dann können Mengenindizes sogar so gebaut werden, dass sie sehr gut in DDD passen, denn dann sind sie nichts anderes als weitere Zweit- und Dritt-Aggregates, die die Ereignisse von anderen Aggregates verwenden. |
+ | >To-Do: Sekundärindex noch in Mengenindex ändern - nur bis hier erledigt | ||
===== Beispiel Zeitdatensätze ===== | ===== Beispiel Zeitdatensätze ===== | ||
Angenommen die Regeln((Invariants)) sind folgende | Angenommen die Regeln((Invariants)) sind folgende | ||
Line 93: | Line 94: | ||
> Das Ergebnis ist aber stärker als bei verteilter schließlicher Konsistenz, weil in dem Beispiel die Änderung von Termin A nur noch nicht beobachtet wurde, sie hat aber bereits statt gefunden. Man kann somit mit der alten Version von Termin A direkt nichts machen, weil man in der Domäne eine ConcurrencyException erhalten würde. | > Das Ergebnis ist aber stärker als bei verteilter schließlicher Konsistenz, weil in dem Beispiel die Änderung von Termin A nur noch nicht beobachtet wurde, sie hat aber bereits statt gefunden. Man kann somit mit der alten Version von Termin A direkt nichts machen, weil man in der Domäne eine ConcurrencyException erhalten würde. | ||
===== Moment mal ===== | ===== Moment mal ===== | ||
- | Der Sekundärindex stellt sicher, dass die Ereignisse für die Aggregates, die den Index betreffen, serialisiert gespeichert werden, und somit auch //in der richtigen Reihenfolge veröffentlicht werden//. Wenn ich also einen Denormalisierer habe, der die aus den Zeitdatensätzen eines Mitarbeiters einen Mitarbeiterkalender erstellt, dann hat dieser Kalender niemals überlappende Zeitdaten. Das ist unmöglich, solange der Denormalisierer die Ereignisses aus dem Event Store in der Reihenfolge abarbeitet, in der sie veröffentlicht werden. | + | Der Sekundärindex stellt sicher, dass die Ereignisse für die Aggregates, die den Index betreffen, serialisiert gespeichert werden, und somit auch //in der richtigen Reihenfolge veröffentlicht werden//((Transaktionen werden über den EventStore pro Aggregate serialisiert. Transaktionen werden ebenso über den IndexStore pro Sekundärindex serialisiert)). Wenn ich also einen Denormalisierer habe, der die aus den Zeitdatensätzen eines Mitarbeiters einen Mitarbeiterkalender erstellt, dann hat dieser Kalender niemals überlappende Zeitdaten. Das ist unmöglich, solange der Denormalisierer die Ereignisses aus dem Event Store in der Reihenfolge abarbeitet, in der sie veröffentlicht werden. |
- | > D.h., dass im Falle des Vorhandenseins eines Sekundärindexes alle Ereignisse den Index betreffend, die aus einem Event Store kommen((was nicht der Fall sein muss)), auch in der Reihenfolge veröffentlicht werden, so dass der Index niemals verletzt sein wird. Wenn man das einhalten kann, dann kann man auch von sofortiger Konsistenz der Regel((die vom Index eingehalten wird)) ausgehen, und nicht nur von schließlicher Konsistenz. | + | > D.h., dass im Falle des Vorhandenseins eines Sekundärindexes alle Ereignisse den Index betreffend, die aus einem Event Store kommen((was nicht der Fall sein muss)), auch in der Reihenfolge veröffentlicht werden, so dass der Index niemals verletzt sein wird. Wenn man das einhalten kann, dann kann man auch von sofortiger Konsistenz der Regel((die vom Index eingehalten wird)) ausgehen, und nicht nur von schließlicher Konsistenz. Man muss also noch folgende Regel definieren und einhalten: **Alle Aggregates, die einen Sekundärindex verwenden, müssen in ein und demselben EventStore gespeichert werden.** Dadurch werden über den IndexStore alle Ereignisse dieser Aggregates den Index betreffend auch im EventStore serialisiert, was dazu führt, dass man bei der Behandlung der Ereignisse aus dem EventStore 100% sicher sein kann, dass die Regel, die durch den Index geschützt wird, auch niemals verletzt wird. |
+ | > | ||
+ | > Ist das zu restriktiv? Mitnichten. Die Lösung als ein großes Kalender-Aggregate ist ja noch restriktiver - alles würde über ein einziges Aggregate laufen, und somit auch in einem EventStore liegen müssen. D.h. dass wir uns mit einem Sekundärindex nicht stärker einschränken als ohne. Im Gegenteil. Die Ereignisse müssen zwar weiterhin im selben Event Store gespeichert werden, aber die Aggregates werden kleiner und somit flexibler, und nur bestimmte Index-betreffende Ereignisse müssen Aggregate übergreifend behandelt werden. | ||
===== Und zum Abschluss ===== | ===== Und zum Abschluss ===== | ||
Eine schließliche Konsistenz im verteilten Mitarbeiterkalender((also ohne Sekundärindex)) herzustellen ist nicht so ohne. Wie stelle ich sie her? E-Mail an Mitarbeiter, dass es eine Überschneidung gibt? Zeitdatensätze nach einer Neuterminierung automatisch in einen Status //wird geprüft// versetzen, und dann einen Event-Handler bauen, der die Prüfungen serialisiert, und ihn auf //geprüft// oder //Konflikt// setzt - nach der Regel, wer zuerst kam, wird zuerst auf //geprüft// gesetzt. | Eine schließliche Konsistenz im verteilten Mitarbeiterkalender((also ohne Sekundärindex)) herzustellen ist nicht so ohne. Wie stelle ich sie her? E-Mail an Mitarbeiter, dass es eine Überschneidung gibt? Zeitdatensätze nach einer Neuterminierung automatisch in einen Status //wird geprüft// versetzen, und dann einen Event-Handler bauen, der die Prüfungen serialisiert, und ihn auf //geprüft// oder //Konflikt// setzt - nach der Regel, wer zuerst kam, wird zuerst auf //geprüft// gesetzt. | ||
\\ \\ | \\ \\ | ||
Alternativ erlaubt man die Möglichkeit einer Überschneidung ohne speziellen Status, und die nachgelagerten Aktionen prüfen auf Überschneidungen. Z.B. würde das Drucken von Reports, das Erstellen von Monatsabschlüssen und das Abrechnen von Zeiten verhindert werden, wenn es Konflikte gibt. Dann gibt es einfach eine Meldung, und der Mitarbeiter muss den Konflikt lösen bevor die Zeiten weiter verarbeitet werden können. | Alternativ erlaubt man die Möglichkeit einer Überschneidung ohne speziellen Status, und die nachgelagerten Aktionen prüfen auf Überschneidungen. Z.B. würde das Drucken von Reports, das Erstellen von Monatsabschlüssen und das Abrechnen von Zeiten verhindert werden, wenn es Konflikte gibt. Dann gibt es einfach eine Meldung, und der Mitarbeiter muss den Konflikt lösen bevor die Zeiten weiter verarbeitet werden können. | ||
+ | ==== Und noch was ==== | ||
+ | Ein Sekundärindex, wie hier beschrieben, dient eigentlich nur dazu, ein Aggregate, das eines sein sollte, aufzubrechen in mehrere Aggregates. Das bedeutet, dass der Index im Grunde fest auf die //eigentliche Aggregate Id// geht, im Beispiel des Mitarbeiterkalenders auf die Mitarbeiter-Id. Das bedeutet auch, dass die Id, auf die sich der Index bezieht, unveränderlich((immutable)) sein muss, ansonsten hat das Ganze wenig Sinn. Die Aufteilung ist //künstlich//, weil die Regel den gesamten Kalender betrifft, aber ebenso //natürlich// und //echt//, weil man die Einträge einzeln betrachtet, und nicht den gesamten Kalender samt aller Einträge bemühen muss, wenn man in einem Eintrag nur den Kommentar ändert. | ||
+ | \\ \\ | ||
+ | Gegen eine Aufteilung des Aggregates in ein Kalender-Aggregate, das nur den Zeitraum des Termins betrifft, und ein ein Detail-Aggregate, das alle weiteren Angaben betrifft, spricht, dass diese Aufteilung unnatürlich ist. Ein Zeitdatensatz wird z.B. als Ganzes abgerechnet. Wenn der Zeitdatensatz einen Status hat, der eine Bearbeitung nicht mehr erlaubt, dann gilt das für den Datensatz im Kalender, und auch für die Details des Datensatzes. | ||
+ | \\ \\ | ||
+ | Ein Domain Service könnte natürlich eine Statusänderung immer auf beide Teile anwenden. Was, wenn es aber eine Abweichung gibt, und der Zeitdatensatz den Status //erstellt// hat, der terminierte Datensatz im Kalender den Status //abgelehnt//? Was anzeigen, welche Statusänderungsrechte prüfen? | ||
+ | \\ \\ | ||
+ | Alternativ könnte jede Änderung im terminierten Teil, also im Mitarbeiterkalender, über einen Domain Service laufen. Der Domain Service würde den Eintrag im Kalender verschieben, und im Zeitdatensatz selbst nur den Befehl AllowRescheduling(von-bis) ausführen. Der Zeitdatensatz müsste dann das Ereignis ReschedulingAllowed(id, von-bis) veröffentlichen, damit alle Prüfungen innerhalb des Domain Service Aufrufs über beide Aggregates geschützt sind. | ||
+ | \\ \\ | ||
+ | Die Lösung in diesem Fall laut //Blue Book// ist, auf eine transaktionale Konsistenz zu verzichten, und die Regel, dass es keine Zeitüberschneidung geben darf, nur schließlich konsistent einzuhalten. Oder man macht aus dem gesamten Kalender samt aller Zeitdatensätze und Details ein (potenziell) riesiges Aggregate. Ich suche Wege, das zu verhindern. Solch ein Sekundärindex ist ein Weg, der mir fast natürlicher erscheint als den Zeitdatensatz in zwei Aggregates aufzuteilen und einen Domain Service zu verwenden, der in diversen Fällen beide anfassen muss. Was ist ein Zeitdatensatz ohne Zeitangaben, und was ist ein terminierter Datensatz ohne dazu gehörige Details? | ||
+ | \\ \\ | ||
+ | Also halte ich an dem Sekundärindex fest, und ja, er soll auch im Aggregate gesetzt werden, und nicht versteckt im Event Handler, denn die Regel ist ja fester Bestandteil der Domäne, und nicht was rauf geflaschtes. |