Strategic Design bietet eine etablierte Methodik zum Schneiden großer Anwendungen in möglichst autarke Domänen. Nx erlaubt die Umsetzung dieser Domänen in einem Angular Monorepo und stellt sicher, dass dabei möglichst wenig Abhängigkeiten entstehen. Ausgewählte Ideen von Tactical Design unterstützen dabei.
Angular bietet sich aufgrund seiner Struktur und des gebotenen Leistungsumfangs für die Umsetzung großer Frontends an. Um solche Systeme beherrschbar zu gestalten, gilt es, sie in kleine und wenig komplexe Module zu untergliedern. Das ist soweit bekannt. Die Frage, die sich hierbei jedoch immer wieder aufdrängt, ist, nach welchen Kriterien der Modulschnitt erfolgen soll. Außerdem gilt es, festzulegen, wie diese Module zu implementieren sind, aber auch, wie sie untereinander kommunizieren können.
Dieser Artikel gibt zunächst eine Antwort basierend auf den Ideen des Strategic Designs aus DDD. Danach zeigt der Artikel, wie sich eine solche Architektur mit Nx [1], einer populären und freien Erweiterung für die Angular CLI, umsetzen lässt. Außerdem besprechen wir ausgewählte Ideen aus Tactical Design, die bei der Umsetzung helfen. Die verwendeten Beispiele finden sich wie immer in meinem GitHub-Account [2].
Vertikale und horizontale Trennlinien
Domain Driven Design sieht vor, dass ein Gesamtsystem in mehrere kleine, möglichst autarke Subdomänen zu untergliedern ist. Dieses Vorgehen nennt sich auch Strategic Design.
Sind diese Subdomänen erst einmal identifiziert, stellt sich die Frage, wie sie strukturiert werden sollen. Eine klassische Vorgehensweise sieht die Unterteilung in Schichten vor. Diesen Ansatz verfolgt auch der vorliegende Text (Abb. 1).

Alternativ zur Schichtentrennung lassen sich auch eine hexagonale Architektur oder Ideen aus Clean Architecture einsetzen. Dank des in Angular integrierten Dependency-Injection-Mechanismus gestalten sich auch solche Implementierungen sehr gradlinig.
Wie Abbildung 1 zeigt, führt die verfolgte Vorgehensweise zu einer vertikalen Unterteilung nach Subdomänen und zu einer zusätzlichen horizontalen Unterteilung nach Schichten. Für jene Aspekte, die domänenübergreifend zu nutzen sind, kommt ein zusätzlicher vertikaler Abschnitt mit der Bezeichnung „shared“ zum Einsatz. Seine fachlichen Teile entsprechen dem von DDD vorgeschlagenen Shared Kernel. Zusätzlich beherbergt er technische Bibliotheken, z. B. für Authentifizierung und Logging.
Jede Schicht erhält nun eine oder mehrere Bibliotheken. Zugriffsregeln zwischen diesen Bibliotheken führen zu einer losen Kopplung und somit zu einer gesteigerten Wartbarkeit.
Typischerweise legt man fest, dass jede Schicht nur mit darunterliegenden Schichten kommunizieren darf, aber auch, dass domänenübergreifende Zugriffe lediglich über den Shared-Bereich erlaubt sind. Um zu verhindern, dass zu viel im Shared-Bereich landet, nutzt der hier vorgestellte Ansatz auch APIs, die Building Blocks für andere Domänen veröffentlichen. Das entspricht der Idee von Open Services in DDD.
In Anlehnung an angular-enterprise-monorepo-patterns [3] unterscheidet der hier vorgeschlagene Ansatz zwischen fünf Kategorien von Schichten bzw. Bibliotheken (Tabelle 1).
Kategorie | Beschreibung | Beispielhafte Inhalte |
---|---|---|
feature | beinhaltet Komponenten für einen Use Case | book-flight-component |
api | exportiert Building Blocks aus der aktuellen Subdomäne für andere | flight (aus Domain-Schicht) |
ui | beinhaltet sogenannte „dumme Komponenten“ (Dumb Components), die Use-Case-agnostisch sind und somit wiederverwendet werden können | datetime-component address-component adress-pipe |
domain | beinhaltet jene Teile des Domänenmodells, die clientseitig zum Einsatz kommen | flight passenger |
util | beinhalten allgemeine Hilfsfunktionen | formatDate |
Tabelle 1: Kategorisierung von Schichten und Bibliotheken
Die vollständige Architekturmatrix wirkt ein wenig erdrückend, aber wie so oft, wird auch hier nichts so heiß gegessen, wie es gekocht wird. Wie die ausgegrauten Blöcke in Abbildung 1 andeuten, befinden sich die meisten Util-Bibliotheken nur im Shared-Bereich, zumal Aspekte wie Authentifizierung und Logging systemübergreifend zum Einsatz kommen sollen. Dasselbe gilt auch für allgemeine UI-Bibliotheken, die ein systemweites Look and Feel sicherstellen.
Die Use-Case-spezifischen Featurebibliotheken und die domänenspezifischen Domainbibliotheken befinden sich hingegen in der Regel nicht im Shared-Bereich. Das wäre zwar im Sinne eines Shared Kernels konform zu Ideen von DDD; da es jedoch zu geteilten Verantwortungsbereichen, mehr Abstimmungsaufwand und Breaking Changes führen kann, sollte damit sparsam umgegangen werden.
Die Domäne isolieren
Um die Domänenlogik zu isolieren, werden ihr Fassaden [4] vorangestellt. Diese bereiten die Domänenlogik für jeweils einen Use Case auf und kümmern sich auch um die Verwaltung von Zuständen (Abb. 2).

Während Fassaden genau hierfür gerade im Angular-Umfeld sehr beliebt sind, korreliert diese Idee auch wunderbar mit DDD, wo von Application Services die Rede ist. Auch Infrastrukturangelegenheiten werden von der eigentlichen Domänenlogik getren...