Warum Layering?
Eine Anwendung lässt sich auch ohne bewusstes Layering entwickeln. Components können Daten laden, Services können Zustand halten, Stores können HTTP-Aufrufe ausführen und irgendwo dazwischen findet sich die Businesslogik.
Das funktioniert.
Zumindest eine Zeit lang.
Mit wachsender Anwendung wird jedoch eine andere Frage immer wichtiger: Wo gehört eine bestimmte Verantwortung eigentlich hin?
Genau hier beginnt Layering.
Layering ist keine neue Idee
Abschnitt betitelt „Layering ist keine neue Idee“Die Trennung einer Anwendung in unterschiedliche Verantwortungsbereiche gehört zu den etablierten Grundideen der Softwarearchitektur.
Martin Fowler beschreibt mit Presentation Domain Data Layering beispielsweise die Trennung von Darstellung, Fachlogik und Datenzugriff. Ein wesentlicher Vorteil liegt darin, dass Entwickler sich jeweils auf einen begrenzten Ausschnitt des Systems konzentrieren können: Wer an Fachlogik arbeitet, muss sich nicht gleichzeitig mit den Details der Benutzeroberfläche oder der Datenbeschaffung beschäftigen.
Domain-Driven Design verwendet wiederum Begriffe wie User Interface, Application, Domain und Infrastructure – Eric Evans beschreibt diese Schichten in seiner DDD Reference. Andere Architekturansätze wählen andere Darstellungen. Alistair Cockburns Hexagonal Architecture spricht beispielsweise weniger über ein Oben und Unten als über Innen und Außen: Fachliche Logik soll nicht davon abhängen, ob sie durch eine grafische Oberfläche, einen Test oder einen anderen technischen Adapter angesprochen wird. Ebenso soll sie nicht an eine konkrete Datenbank oder einen bestimmten externen Dienst gekoppelt sein.
Die konkreten Namen, Grenzen und Diagramme unterscheiden sich. Die Grundidee bleibt ähnlich:
Unterschiedliche Verantwortlichkeiten sollten nicht unkontrolliert ineinanderlaufen.
Inspiration statt Dogma
Abschnitt betitelt „Inspiration statt Dogma“Eine wichtige Inspiration für mein Verständnis von Frontend-Layering waren die Arbeiten von Manfred Steyer zu DDD-inspirierten Angular- und Frontend-Architekturen.
Die in dieser Serie verwendete Struktur übernimmt diese Ansätze nicht unverändert. Sie verbindet diese Inspiration mit Erfahrungen aus realen Projekten, langfristiger Wartung und der Zusammenarbeit in Entwicklungsteams.
An einigen Stellen weicht meine Interpretation bewusst von klassischeren DDD-Modellen ab. Besonders deutlich wird das bei +state: Dieser Bereich übernimmt einen großen Teil der Fachlichkeit. Hier leben Business Rules, fachliche Ableitungen und Zustandsübergänge. Die Application-Schicht bleibt dagegen bewusst flach und viewnah.
Die folgenden Artikel versuchen deshalb nicht, das richtige DDD-Layering für Frontends zu definieren. Sie beschreiben ein DDD-inspiriertes Modell, das sich in der Praxis bewährt hat und vor allem eine alltägliche Frage beantworten soll:
Welche Verantwortung gehört wohin?
Das eigentliche Problem sind nicht die Ordner
Abschnitt betitelt „Das eigentliche Problem sind nicht die Ordner“Layering wird häufig zuerst als Verzeichnisstruktur sichtbar. Aus klassischen Backend-Anwendungen kennen viele beispielsweise Strukturen wie:
src/├── controllers/├── services/├── repositories/└── entities/Controller nehmen Requests entgegen, Services koordinieren Anwendungslogik und Repositories kümmern sich um Persistenz. Unterschiedliche Verantwortlichkeiten bekommen damit zunächst unterschiedliche Orte.
Eine solche Struktur ist hilfreich. Sie ist aber noch keine Architektur.
Die Verzeichnisse zeigen zunächst nur, welche Verantwortungsbereiche vorgesehen sind. Ob daraus tatsächlich eine belastbare Architektur entsteht, entscheidet sich im Code.
Für jeden Bereich muss klar sein, welche Verantwortung dort liegt und an welcher Stelle sie endet. Erst wenn diese Grenzen im Team verlässlich verstanden und eingehalten werden, wird aus einer Ordnerstruktur eine Architektur.
Weniger Entscheidungen im Alltag
Abschnitt betitelt „Weniger Entscheidungen im Alltag“Eine gute Architektur verhindert nicht jede Diskussion. Sie sollte aber verhindern, dass dieselben grundlegenden Diskussionen bei jedem Feature erneut geführt werden.
Wo gehört eine Business Rule hin?
Wer darf HTTP aufrufen?
Wo entsteht ein ViewModel?
Darf eine Component direkt auf den Store zugreifen?
Wer aggregiert mehrere fachliche Datenströme?
Wo gehört eine Ableitung aus vorhandenem Zustand hin?
Ohne gemeinsame Regeln werden solche Fragen von Entwickler zu Entwickler und Feature zu Feature unterschiedlich beantwortet. Architektur entsteht dann schleichend aus lokalen Einzelentscheidungen.
Klare Layergrenzen schaffen Leitplanken. Nicht jede Entscheidung muss neu getroffen werden.
Das reduziert Diskussionen und macht eine Codebasis vorhersehbarer. Wer ein unbekanntes Feature öffnet, sollte bereits eine gute Vorstellung davon haben, wo eine bestimmte Art von Logik zu finden ist, bevor die erste Datei geöffnet wurde.
Layering begrenzt den Denkraum
Abschnitt betitelt „Layering begrenzt den Denkraum“Dieser Effekt wird häufig unterschätzt.
Wenn ich an einer Presentation arbeite und weiß, dass dort keine Business Rules implementiert werden, muss ich einen großen Teil der Anwendung in diesem Moment nicht verstehen.
Wenn ich eine fachliche Regel ändere, möchte ich nicht gleichzeitig darüber nachdenken müssen, welches UI-Framework gerade verwendet wird.
Wenn ich Daten aus einem Backend integriere, sollte mich nicht interessieren müssen, mit welchem Button sie später verändert werden.
Layering verkleinert damit den Kontext, den wir für eine konkrete Änderung gleichzeitig im Kopf behalten müssen. Das hilft bei der Implementierung ebenso wie beim Debugging, bei Reviews, beim Onboarding und bei Refactorings.
Wer weiß, welche Verantwortung in welchem Bereich liegt, muss weniger vom gesamten System verstehen, um eine lokale Änderung sicher durchführen zu können.
Das gilt für Menschen – und zunehmend auch für Werkzeuge.
Leitplanken für AI Agents
Abschnitt betitelt „Leitplanken für AI Agents“Mit AI-gestützter Entwicklung bekommt diese Eigenschaft eine zusätzliche Bedeutung.
Ein Agent kann sehr schnell Code erzeugen. Er kennt aber nicht automatisch die Architekturentscheidungen eines konkreten Projekts. Ohne klare Grenzen sind zunächst viele Varianten plausibel:
HTTP im Store?Business Rule in der Component?Mapping in der Facade?Navigation aus dem State?Technisch lassen sich all diese Varianten implementieren. Entscheidend ist, welche davon zur Architektur der Anwendung gehört.
Eine klar definierte Architektur reduziert diesen Lösungsraum.
Wenn feststeht, dass Business Rules in +state liegen, Infrastructure die technische Außenwelt kapselt und Presentation frei von Fachlogik bleibt, lassen sich diese Regeln nicht nur Menschen vermitteln. Sie können auch in Architektur-Dokumentation, Agent Instructions, Generatoren, Dependency Constraints, Reviews und statischen Prüfungen abgebildet werden.
Architektur muss dann nicht bei jedem generierten Codefragment neu ausgehandelt werden.
Ein Agent erhält nicht nur die Aufgabe:
Implementiere dieses Feature.
Er erhält zusätzlich die Leitplanken:
Implementiere dieses Feature innerhalb dieser Architektur.
Je schneller Code erzeugt werden kann, desto wichtiger wird die Frage, welche Regeln seine Struktur begrenzen.
Trennung schafft Möglichkeiten
Abschnitt betitelt „Trennung schafft Möglichkeiten“Saubere Grenzen dienen nicht nur der Ordnung. Sie schaffen Optionen.
Wenn Businesslogik nicht in Components steckt, kann dieselbe Fachlichkeit durch unterschiedliche Oberflächen verwendet werden. Eine Angular-Material-Presentation, eine Ionic-Presentation oder eine andere Oberfläche müssen dieselben fachlichen Entscheidungen nicht erneut implementieren.
Das ist kein rein theoretisches Szenario. In einem meiner Langzeitprojekte wurden Angular Material und Ionic über längere Zeit parallel auf derselben fachlichen Basis betrieben.
Das funktionierte, weil die Grenze konsequent eingehalten wurde: Die Presentation durfte die Fachlichkeit darstellen und Benutzerinteraktionen entgegennehmen, aber keine zweite Version der Businesslogik entwickeln.
Austauschbarkeit ist dabei nicht zwangsläufig das eigentliche Ziel. Niemand sollte eine Architektur nur deshalb komplexer machen, weil theoretisch irgendwann ein UI-Framework ersetzt werden könnte.
Die Möglichkeit zur Austauschbarkeit ist vielmehr ein guter Test für die Qualität einer Grenze:
Wie viel müsste sich ändern, wenn sich ausschließlich die Darstellung oder ausschließlich eine technische Anbindung ändert?
Je mehr fachlich unbeteiligter Code dabei betroffen ist, desto weniger sauber war die Verantwortung getrennt.
Dasselbe gilt für technische Kommunikation. Wenn Infrastructure sauber gekapselt ist, muss die Fachlichkeit nicht wissen, ob Daten über HTTP, lokalen Speicher oder einen anderen Mechanismus bereitgestellt werden.
Wenn fachlicher Zustand eine klare Heimat besitzt, müssen Ableitungen und Regeln nicht über Components, Services und Views verteilt werden.
Unser Layering im Frontend
Abschnitt betitelt „Unser Layering im Frontend“Die folgenden Artikel verwenden ein bewusst frontendorientiertes und von klassischem DDD abweichendes Modell.
Die organisatorische Struktur eines Features sieht beispielsweise so aus:
my-feature/├── domain/│ ├── infrastructure/│ ├── +state/│ └── application/├── models/└── presentation/domain ist hier ein organisatorischer Oberbegriff für den nicht-visuellen Kern des Features. Er ist nicht mit dem klassischen Domain Layer aus DDD gleichzusetzen.
Auch die Verzeichnisstruktur selbst bildet nicht den Datenfluss ab.
Für die folgenden Artikel betrachten wir den Weg entlang der Grenzen, die Daten auf ihrem Weg von der technischen Außenwelt bis zur Benutzeroberfläche durchlaufen:
Technische Außenwelt ↓Infrastructure ↓ +State ↓ Application ↓ Presentation ↓ BenutzerDabei hat jeder Bereich eine klar umrissene Aufgabe.
Infrastructure kapselt die technische Außenwelt und übersetzt technische Kommunikation in eine Form, mit der das Feature arbeiten kann.
+State bildet den fachlichen Laufzeitzustand ab. Hier leben Business Rules, Zustandsübergänge, fachliche Ableitungen und Selektionen.
Application bleibt bewusst dünn. Für jede View sammelt eine eigene Facade vorhandene Datenflüsse ein und stellt daraus einen stabilen, view-spezifischen Contract für die Presentation bereit.
Presentation kümmert sich um Darstellung und Benutzerinteraktion, ohne fachliche Regeln erneut zu interpretieren.
models nimmt dabei eine besondere Rolle ein. Manche Modelle überschreiten bewusst Layergrenzen. Ein ViewModel kann beispielsweise von der Application erzeugt und von der Presentation konsumiert werden, ohne deshalb vollständig einem der beiden Bereiche zu gehören.
Dieses Modell ist keine universelle Wahrheit und kein Versuch, Domain-Driven Design unverändert auf das Frontend zu übertragen.
Es ist meine, unter anderem von Manfred Steyers Arbeiten inspirierte Interpretation eines Frontend-Layerings, ergänzt um Erfahrungen aus realen Projekten und langfristiger Anwendung.
Vor allem ist es eine Leitplanke dafür, wo Verantwortung beginnt und wo sie endet.