© Liashko/Shutterstock.com
Nachhaltige Angular-Architekturen mit Strategic und Tactical Design

DDD im Frontend


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).

steyer_ddd_1.tif_fmt1.jpgAbb. 1: Domänen und Layer

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).

steyer_ddd_2.tif_fmt1.jpgAbb. 2: Den Domain Layer isolieren

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...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang