DDD & Co., Teil 4: Aggregates

the next big thing  –  0 Kommentare

Nachdem in den vergangenen Folgen die Kommandos und fachlichen Ereignisse definiert und in einem begrenzten Kontext untergebracht wurden, fehlt als letztes wichtiges Konzept von DDD noch das Aggregat. Üblicherweise werden Aggregate als transaktionale Konsistenzgrenzen erklärt – doch was bedeutet das?

Wer sich mit DDD befasst, begegnet über kurz oder lang dem Konzept des Aggregats (englisch: Aggregate). Aggregate bilden das Fundament, um Kommandos in fachliche Ereignisse zu transformieren, und spielen daher eine tragende Rolle. Zugleich sind sie jedoch häufig das am schwierigsten zu verstehende Konzept von DDD.

Dabei lassen sich Aggregate eigentlich sehr einfach und anschaulich erklären. Sendet ein Anwender Kommandos an das System, entstehen daraus fachliche Ereignisse. Die Umwandung erfolgt anhand gewisser Regeln, die die Konsistenz innerhalb des Systems sicherstellen sollen.

Wichtig dabei ist, zu klären, welche Änderungen welchen Regeln unterliegen. Während einige Änderungen unabhängig voneinander sind und deshalb zeitgleich stattfinden dürfen, müssen andere nacheinander und kontrolliert durchgeführt werden, da sie einander beeinflussen könnten. Eine der wichtigsten Fragen in DDD lautet daher, welche Änderungen in einer gemeinsamen Transaktion ausgeführt werden müssen, und welche getrennt voneinander funktionieren.

Je weniger Aspekte eine Transaktion umfasst, desto besser skaliert das System, weil sich zeitgleiche Änderungen nicht in die Quere kommen. Kleinere Transaktionen bedeuten zugleich aber auch geringere Auswirkung von Änderungen in der Anwendung. Die Grenzen einer Transaktion dürfen also nicht zu klein gewählt werden, da sonst die Konsistenz leidet – zu groß aber auch nicht, da sonst die Skalierbarkeit leidet.

Ein Aggregat agiert als eine solche Transaktionsgrenze und wird daher auch als transaktionale Konsistenzgrenze bezeichnet. Implementiert werden Aggregate häufig als Objekte, das ist aber nicht zwingend erforderlich. Genauso gut könnten sie rein als Funktionen umgesetzt werden.

Auf das Beispiel der To-do-Liste bezogen, stellt sich die Frage, ob die einzelnen Aufgaben eigenständige Aggregate sind, oder ob die ganze Liste ein einziges Aggregat darstellt. Für beide Varianten gibt es Argumente für und gegen ihren Einsatz:

  • Definiert man die Liste als ein einziges Aggregat bietet das den Vorteil, dass man die Konsistenz innerhalb der Liste garantieren kann. Dadurch lässt sich beispielsweise sicherstellen, dass die Liste zu keinem Zeitpunkt mehr als fünf nicht erledigte Aufgaben enthält.
  • Zugleich bedeutet das aber auch, dass Kommandos wie note, tick off und resume nicht zeitgleich ausführbar sind, da sie dieselbe transaktionale Konsistenzgrenze betreffen. Das mindert die Leistungsfähigkeit, wenn viele Anwender gleichzeitig auf die Liste zugreifen möchten.
  • Ist diese Skalierbarkeit wichtig, spricht das also eher dafür, jede einzelne Aufgabe als eigenständiges Aggregat zu definieren. Die Kommandos note, tick off und resume funktionieren für verschiedene Aufgaben dann unabhängig voneinander und lassen sich daher auch zeitgleich ausführen.
  • Das wiederum bedeutet aber, dass es schwieriger wird, die zuvor erwähnte Konsistenz der Liste zu garantieren.

Da es in der als Beispiel gewählten Anwendung TodoMVC keine Regeln gibt, die die Konsistenz der Liste betreffen, gibt es keinen Grund, sie als ein einziges großes Aggregat zu implementieren. Tatsächlich gibt es sogar nur eine einzige Liste, weshalb sie selbst gar nicht modelliert werden muss.

Das zeigt sich auch an den zuvor definierten fachlichen Ereignissen und Kommandos, die sich allesamt ausschließlich auf einzelne Aufgaben beziehen. Auffällig in der grafischen Benutzeroberfläche ist, dass die Möglichkeit besteht, alle Aufgaben auf einen Schlag als erledigt zu markieren. Ist das nicht ein Hinweis darauf, dass die Liste eventuell doch als ein einziges großes Aggregat modelliert werden sollte?

Tatsächlich hängt die Antwort auf diese Frage davon ab, wie das Kommando zu verstehen ist. Was soll beispielsweise geschehen, wenn ein Teil der Aufgaben zum Beispiel auf Grund eines technischen Fehlers nicht als erledigt markiert werden kann? Sind die übrigen dann trotzdem als erledigt zu markieren oder muss deren Abhaken zurückgenommen werden?

Im ersten Fall wäre die Möglichkeit, alle Aufgaben als erledigt zu markieren, nichts anderes als eine Komfortfunktion, die dem Anwender erspart, jede Aufgabe einzeln abhaken zu müssen. Im zweiten Fall wäre sie ein eigenständiges Konzept der Fachdomäne. Dann würden in der derzeitigen Modellierung aber fachliche Ereignisse fehlen, die diesen Fall beschreiben.

Die Frage nach dem Umgang mit partiellen Fehlern ist oftmals hilfreich, um zu entscheiden, wie klein oder groß Aggregate zu gestalten sind. Als Faustregel kann man aber davon ausgehen, dass kleine Aggregate in der Regel von Vorteil sind.

Letztlich lässt sich die Frage, welchem von beiden Vorgehen der Vorzug zu geben ist, aber nicht aus technischer Sicht beantworten: Es handelt sich um eine fachliche Frage, die man deshalb an einen Fachexperten richten muss. Mangels einer verbindlichen Antwort für TodoMVC wird im weiteren Verlauf davon ausgegangen, dass partielle Fehler akzeptabel sind und daher auf ein Aggregat für die Liste verzichtet werden kann.

Übrig bleibt also ein kleines und überschaubares Aggregat, das die Logik für eine einzelne Aufgabe enthält. Als Name für das Aggregat bietet sich todo an, sodass folgende Struktur entsteht:

  • planning (begrenzter Kontext)
    • todo (Aggregat)
      • note => noted
      • edit => edited
      • tick off => ticked off
      • resume => resumed
      • discard => discarded
      • archive => archived

Damit ist die Modellierung von TodoMVC abgeschlossen. Die nächste Frage lautet, wie sich dieses mit Hilfe von DDD entwickelte Domänen-Modell in Code implementieren lässt. Dazu ist zunächst herauszufinden, wie sich das Modell persistieren lässt. Einen potenziellen Ansatz stellt das Konzept des Event Sourcing dar, um das es daher in der nächsten Folge gehen wird.

tl;dr: Ein Aggregat kümmert sich um die Konsistenz in einem Bereich der Anwendung, weshalb Aggregate auch als transaktionale Konsistenzgrenzen bezeichnet werden. Im Sinne einer guten Skalierbarkeit sind kleine Aggregate vorzuziehen.