User Tools

Site Tools


technology:domainmodel:aggregatedesign

This is an old revision of the document!


Aggregate Design

Ein Aggregate ist eine Konsistenzgrenze1). Das heißt, dass alle Regeln innerhalb des Aggregates immer2) eingehalten werden können. Das heißt aber gleichzeitig, dass alle Regeln zwischen Aggregates nicht immer sofort eingehalten werden können, sondern im besten Fall so gut es geht und schließlich3).

Ein einfaches Beispiel ist die Auftragsnummer. Klassisch ist die Auftragsnummer Teil des Auftrags. Dieser wird in der Datenbank als eine Zeile in der Tabelle ORDERS gespeichert, und auf dem Feld OrderKey gibt es einen eindeutigen Index. Hierzu gibt es zwei Bemerkungen4).

Erstens ist das ziemlich kurz gedacht. Soll man die Auftragsnummer ändern können? Wenn ja, gelten bereits verwendete Auftragsnummern dann als verbraucht? Wenn ich z.B. in einem Auftrag die Auftragsnummer von AG-20130306 in AG-20130307 ändere, wäre in der Datenbank kein Mechanismus enthalten, dass ein neuer Auftrag die Nummer AG-20130306 erhalten darf - der Index wäre ja nicht verletzt.

Ein Datenbank-Index stellt eine ganz einfachere Regel sicher: Es dürfen keine 2 oder mehrere Zeilen gleichzeitig denselben Wert haben. Der Index repräsentiert eine statische Regel die Struktur betreffend. DDD5) ist aber ein Entwicklungsmuster, dass die Verhaltensweise der Domäne verinnerlicht. Man fragt sich nicht, ob zwei Aufträge dieselbe Nummer haben dürfen, sondern was passieren soll, wenn ein Auftrag eine Nummer erhalten soll die ein anderer bereits hat, bzw. hatte. Es geht darum, welche Aktionen erlaubt sind, basiert auf vorherigen Aktionen, und nicht darum, wie das System punktuell aussehen darf.

Zweitens macht der Index im Grunde aus dem Auftrag eine Entität im Alle-Aufträge Aggregate. Schreiben des Indexes muss serialisiert werden um ihn einhalten zu können. Der Index ist somit ein Aggregate über alle Aufträge. Das reduziert die Partitionstoleranz des Systems, weil die Aufträge alle im selben Knoten liegen müssen.

Die Auftragsnummer

Angenommen es gibt den Auftrag und die Auftrags-Registratur. Wenn der Auftrag Regeln die Auftragsnummer betreffend hat, und die Registratur Regeln die Auftragsnummern betreffend, dann sind das 2 getrennte Regelwerke, von denen lediglich eines konsistent eingehalten werden kann. Abhängig davon, welche Regeln immer und sofort eingehalten werden müssen, gehört die Auftragsnummer entweder in den Auftrag oder in die Auftrags-Registratur. Im zweiten Fall wären die Auftragsnummern Entitäten im Registratur Aggregate, und würden den Auftrag referenzieren.

Auftragsnummer redundant und synchron in mehreren Aggregates halten

Warum nicht einfach die Auftragsnummer in beiden Aggregates halten? Dann könnte man alle Regeln zur Auftragsnummer im Auftrag einhalten, sowie in der Registratur.

Das mag auf den ersten Blick verlockend klingen. Bei näherer Betrachtung ist es aber recht gefährlich. Man muss nämlich Mechanismen einbauen, die die Auftragsnummern in beiden Aggregates synchronisiert. Eine (verteilte) Transaktion wäre dafür ein möglicher Weg. Starte Transaktion, setze dieselbe Auftragsnummer im Auftrag und in der Registratur, und wenn einer der beiden die Nummer ablehnt, storniere die Transaktion. Im Jargon von DDD würde man das über einen Domain Service umsetzen.

Auch hier habe ich einige Bedenken.

Erstens hat man nun zwei Quellen der Wahrheit6). Was ist nun die Auftragsnummer? Das, was im Auftrag steht oder das was in der Registratur steht? Das wäre zwar egal, weil dort immer dasselbe stehen sollte, aber die Gleichheit wird über Mechanismen sicher gestellt, die nicht Teil der Aggregates selbst sind. Bei großen und komplexen Systemen sind solche Mechanismen insofern gefährlich, weil sie vielleicht von einem neuen Entwickler umgangen werden, weil er die Relevanz nicht erkennt. Er erlaubt dann eine Änderung einer Auftragsnummer in der Registratur ohne eine gleichzeitige Änderung im Auftrag. Und schon haben wir den Salat, einen Fehler der nicht vorhersehbar war und nur schwer zu beheben ist.

Regeln

Man sollte also erst mal analysieren, welche Regeln es gibt, und wie sie einzuhalten sind - welche Regeln können schließlich konsistent gemacht werden? Eine pseudo-Regel ist

Man möchte das parallele Arbeiten an den Aufträgen nicht serialisieren. Wenn z.B. gleichzeitig an tausenden von Aufträgen gearbeitet wird, muss es möglich sein, die Änderungen zu speichern, ohne dass es Versionskonflikte hagelt.

Das heißt, dass ein Aggregate über alle Aufträge nicht in Frage kommt.

Zwei Aufträge dürfen niemals dieselbe Auftragsnummer erhalten. Man bedenke eine Systemintegration, in der Aufträge anhand der Auftragsnummer an ein Fremdsystem exportiert wird. Wenn ein neuer Auftrag eine bereits verwendete Auftragsnummer erhält - wenn auch nur vorübergehend - ist eine Korrektur über Systemgrenzen hinweg nur sehr schwer machbar sollte das Fremdsystem die Möglichkeit einer doppelten Auftragsnummer nicht erlauben.

Das heißt, dass die Auftragsnummer nicht schließlich konsistent sein darf.

Umsetzung

Es gibt nur die eine Lösung, dass es zwei Aggregates geben muss. Es gibt den Auftrag und die Auftrags-Registratur. Die Auftragsnummer wird in der Registratur vergeben, und das Ergebnis ist Teil des Auftrags im Lesemodell. Die Auftragsnummernvergabe wird zwar serialisiert, das passiert i.d.R. pro Auftrag nur einmal. Alle anderen Arbeiten am Auftrag passieren isoliert im Auftrags-Aggregate.

Weitere Regeln

Es gibt noch andere Regeln, die denkbar sind. Z.B. könnte eine lauten: Ein Auftrag darf nur freigegeben werden, wenn er eine Auftragsnummer hat. Wie kann diese Regel im Auftrag eingehalten werden, wenn die Auftragsnummer nicht Teil des Auftrags ist?

Das müsste über Workflows gesteuert werden. Die Auftragsfreigabe kann das Lesemodell der Registratur verwenden um zu überprüfen, ob der Auftrag eine7) Auftragsnummer hat. Der Workflow, der hierfür umgesetzt werden muss, ist der, dass die Registratur Auftragsnummern nicht mehr ändern darf. Dann steht einer sicheren Auftragsfreigabe nichts mehr im Wege.

Wenn die Umsetzung mit Domain Services unsicher war, ist das aber ebenso unsicher. Auch hier könnte ein neuer Entwickler8) in die Registratur eine Methode RemoveOrderKey einbauen. Es gäbe dann einen freigegebenen Auftrag ohne Auftragsnummer.

Nun, die Beispiele sind wahrscheinlich sehr naiv. Innerhalb einer Domäne, bzw. eines Bounded Contexts können die Aggregates nicht in Isolation von einander gebaut werden. Es setzt sich ja nicht jemand an ein Aggregate und programmiert es beliebig um. So könnten auch Regeln innerhalb des Aggregates verletzt werden, wenn ein neuer Entwickler nicht weiß, was er tut.

Einen Schritt zurück

Worum geht es bei der Auftragsnummer? Angenommen sie wird dem Auftrag zugeteilt, so wie eine Sozialversicherungsnummer. Dann kann der Auftrag damit nichts anfangen, er ist für die Nummer nicht verantwortlich! Was auch immer ihm zugeteilt wird, akzeptiert er. In dem Fall kann die Auftragsnummer teil des Lesemodell des Auftrags sein, er kann sie ja selbst nicht beeinflussen und hat keine Regeln bezüglich der ihm zugewiesenen Nummer.

Ja, aber was wann ich in meinem System Fremdaufträge akzeptiere? Und diese eintippe? Und mich vertippe? Das muss ich korrigieren können. Auch nachträglich, nachdem alles mögliche mit der Auftragsnummer passiert ist. Trotzdem sollte das System doppelte Auftragsnummern verhindern. Aber in diesem Fall wird die geänderte Nummer für neue Aufträge frei gegeben, denn ich habe mich vielleicht so vertippt, dass ich eine Nummer eines noch einzugebenden Auftrags eingetragen habe.

Die Regeln hängen also auch davon ab, wie das System verwendet werden soll! Ist es ein führendes System? Dann kann man die Auftragsnummer automatisch vergeben und sie darf nicht mehr verändert werden. Ist es nicht das führende System, dann muss die Auftragsnummer frei vergeben werden dürfen, verändert werden dürfen, usw. Soll das somit auch noch pro Kunden konfigurierbar sein?

Beispiel Projektstruktur

Angenommen, man ändert was am blau eingefärbten Projekt. Es wird Regeln geben, die einzuhalten sind, z.B. dass ein internes Projekt kein externes Projekt als Teilprojekt haben darf9). Abhängig der Regel, müssen unterschiedlich viele andere Projekte betrachtet werden, um die Regel einzuhalten.

  1. Es gibt Regeln, für deren Einhaltung man die gesamte Projektstruktur benötigt:
    • Ein Projekt darf nur einmal in der Struktur hängen10)
    • Die Projektnummer muss eindeutig sein
  2. Es gibt Regeln, für deren Einhaltung man die Struktur innerhalb eines Hauptknoten braucht,
  3. Es gibt Regeln, für die man nach unten gucken muss,
  4. Es gibt Regeln, für die man nach oben gucken muss.

Das Problem bei dieser Betrachtung ist, dass man die Projektstruktur strukturell angeht11). Ein Domänenmodell soll aber das gewünschte Verhalten der Domäne bedienen. Was soll die Projektstruktur hergeben?

Verhaltenssicht

  1. Ich möchte ein neues Hauptprojekt anlegen können,
  2. Ich möchte ein neues Teilprojekt zum Hauptprojekt anlegen können,
  3. Die Projektnummer soll automatisch vergeben werden, wenn ich das Projekt anlege oder freigebe,
  4. Die Projektnummernvergabe soll das darüber liegende Projekt mit einbeziehen,
  5. Die Projektnummer soll global eindeutig sein,
  6. Angaben des darüber liegenden Projektes sollen automatisch im darunter liegenden Projekt aufgenommen werden, bei Anlage des Teilprojektes oder bei späteren Änderungen im Projekt darüber,
  7. Aus einem Teilprojekt soll ein Hauptprojekt werden können,
  8. Ein Hauptprojekt soll zum Teilprojekt eines anderen Projektes werden können,
  9. Ebenso ein Teilprojekt

Es stellt sich die Frage nach der Sinnhaftigkeit dieser geforderten Flexibilität. Wie kann man das System immer noch genauso flexibel für den Anwender halten, trotzdem hier und da ein paar Punkte anders lösen. Wenn z.B. bereits Zeiten auf einen Vorgang12) eines Teilprojektes gebucht wurden, stellt sich die Frage, ob man das Teilprojekt einfach beliebig verschieben können soll, womöglich in ein komplett anderes Hauptprojekt eines anderen Kunden.

Eine Frage wäre z.B., ob die Punkte 7. bis 9. nicht besser gelöst werden können, indem man Projekte einfach kopieren kann. Dann kann man ein Hauptprojekt Gehen wir die Punkte mal einzeln durch

  1. dfas

Globales Aggregate für die Projektstruktur

Die Projektstruktur sieht beispielhaft aus wie folgt:

Wenn ein Projekt innerhalb der Struktur verschoben wird, wird die Struktur als Ganzes gesperrt, und es wird geprüft, dass es keine Zirkel gibt. Zirkel kann es geben, wenn Projekt 1 unter Projekt 2 gehängt wird, und gleichzeitig Projekt 2 unter Projekt 1 gehängt wird. Wenn nicht der gesamte Baum gesperrt wird, wären die beiden Aktionen isoliert von einander, und mit Read Committed oder einer andere Art von Abfrage des schließlich konsistenten Lesemodells könnte das während der Prüfung durch rutschen, solange beide Prüfungen beider Aktionen passieren noch bevor eine der beiden fertig ist13).

Möchte man eine global konsistente Sicht auf die Baumstruktur, dann muss die Baumstruktur auch global gesichert werden, d.h. ein universelles Aggregate werden14). Das hat Vorteile, weil es einfach zu handhaben ist. Es hat aber auch Nachteile, denn die Parallelität der Domäne kann darunter leiden.

Ein Aggregate pro Hauptprojekt

Man könnte das globale Aggregate aufteilen auf kleinere Aggregates, und zwar eines pro Hauptprojekt. Das würde dann in etwa so aussehen:

In diesem Fall sind die einzelnen Hauptprojektstrukturen von einander isoliert. Solange ein Projekt die Struktur nicht wechselt, bietet das wiederum eine global konsistente Sicht auf die Domäne, da die Strukturen völlig unabhängig von einander existieren. Das erreicht man, indem man das Projekt zu einer Entität innerhalb des Struktur-Aggregates macht. Ist ein Projekt aber selbst ein Aggregate, und wird nur von der Struktur referenziert, und erlaubt man dem Projekt einen Wechsel von einer Struktur in eine andere, dann ist die globale Sicht nicht mehr konsistent, maximal schließlich konsistent.

Man muss immer daran denken, dass die globale Sicht die Summe der Sichten auf die einzelnen Aggregates ist, und dass die Reihenfolge, in der diese Sicht aufgebaut wird, bzw. in der die Aggregate Sichten mit einbezogen werden, nicht global ist. Wenn somit Projekt 1 von Struktur A in Struktur B verschoben wird, könnte es sein, dass die globale Sicht temporär die Ereignisse von Struktur B beinhaltet, in der das Projekt hinzugefügt wurde, aber noch nicht die Ereignisse von Struktur A in der das Projekt aus der Struktur entfernt wurde15). In der globalen Sicht wäre das Projekt somit temporär gleichzeitig in zwei Strukturen enthalten.

Projekt als Entität in einem Hauptprojekt

Eine konsistente Sicht erhält man, wenn man ein Projekt zu einer Entität einer Projektstruktur macht. Dann kann das Projekt nicht in eine andere Struktur verschoben werden, womit meine Kunden nun bereits über 10 Jahre lang leben16).

Es ist aber irgendwie eine merkwürdige Sicht, ein Projekt nur als Entität einer Projektstruktur zu sehen. Wenn wir die Struktur aber nun das Projekt nennen, und die darin enthaltenen Entitäten Teilprojekte, wäre das schon verständli

1) consistency boundary
2) transaktional
3) Eventual consistency
4) Sicherlich noch viele mehr, aber zwei, die mir zu diesem Thema als relevant einfallen
5) Domain Driven Design
6) “two sources of truth”
7) eindeutige
8) Junior-Entwickler
9) weil das wenig Sinn hat
10) wenn die Struktur ein Projekt lediglich referenziert
11) was der Name ja im Grunde andeutet
12) hätten wir es doch damals Arbeitspaket genannt!
13) Connection.Commit
14) pro Mandant
15) nicht vergessen, jedes Aggregate hat eine eigene, isolierte Sicht auf das Geschehene
16) und nicht einmal nach der Möglichkeit gefragt haben, ein Projekt unter ein anderes Hauptprojekt zu hängen
technology/domainmodel/aggregatedesign.1362570460.txt.gz · Last modified: 2013/03/06 12:47 by rtavassoli