Domain-Driven Design im Frontend

AUF ZU NEUEN WELTEN!

Domain-Driven Design im Frontend

Wann immer die Rede von Domain-Driven Design ist, wird üblicherweise implizit davon ausgegangen, dass es sich um Backend-Code handelt. Aber ist das wirklich eine notwendige Voraussetzung?

In der Geschichte der Softwareentwicklung gibt es zwei sich beständig abwechselnde Paradigmen: „Thin Client“ (d. h., der Code läuft hauptsächlich auf einem Server, die Clients sind leistungsschwach) und „Fat Client“ (d. h., der Client ist in der Lage, ganze Anwendungen auszuführen). Gab es ursprünglich nur Server mit Terminals, brachte der PC die Wende hin zum lokalen Arbeitsgerät, doch der Siegeszug des Webs sorgte dafür, dass so mancher PC nur noch dazu benutzt wurde, mit Hilfe des Browsers Webseiten anzuzeigen, die auf einem leistungsfähigen Server erstellt worden waren. Doch auch hier endete die Entwicklung nicht, denn Frameworks und Libraries wie Angular oder React ermöglichten wieder einmal die Verlagerung von Code in den Client, diesmal in Gestalt von Single Page Applications mit dem Browser als Ausführungsplattform. Dieser Artikel möchte beleuchten, wie sich Domain-Driven Design in einer solchen Single Page Application etablieren lässt und wie sich die verschiedenen DDD-Konzepte übertragen lassen.

ist freiberufliche Softwareentwicklerin und Softwareentwicklungscoach mit umfangreichem Hintergrund in Compilerbau und formalen Verifikationsmethoden. Neben Specification by Example, Domain-Driven Design, React/Redux und der Sanierung von Legacy-Code-Applikationen gehört auch funktionale Programmierung zu ihrem Repertoire. Des Weiteren ist sie Fachbeiratsmitglied bzw. Mitorganisatorin mehrerer Konferenzen sowie einer Fachzeitschrift und Mitbegründerin der Softwerkskammer, einer deutschsprachigen Usercommunity zum Thema Software Craftsmanship.

Strategic Patterns

Die Strategic Patterns in DDD dienen dazu, die verschiedenen fachlichen Teilbereiche – genannt Subdomänen – einer Applikationslandschaft herauszuarbeiten und die bei der Umsetzung entstehenden Bounded Contexts voneinander abzugrenzen und zu trennen. Diese Trennung kann unterschiedlich realisiert werden, angefangen bei der Verabredung, bestimmte Codebereiche innerhalb eines Monolithen voneinander zu isolieren (man spricht in diesem Zusammenhang auch von einem Modulithen), bis hin zur tatsächlichen physischen Aufteilung in separate Deployment-Einheiten, die dann je nach Größe als Microservices oder Self-contained Systems bezeichnet werden. Die allgemein verbreitete Idee ist, dass jeder Bounded Context seine eigenen UI-Bestandteile mitbringt, sodass jedes dieser Frontends auf natürliche Weise nur zu einem Bounded Context gehört und keine weitere Untergliederung dieser Frontends mehr erforderlich ist.

Wenn wir allerdings über eine Single Page Application als quasi einzige Implementierung sprechen, d. h., wenn das Backend nur eine sehr untergeordnete Rolle spielt, ist es durchaus möglich, dass diese SPA groß genug ist, um in mehrere Bounded Contexts unterteilt zu werden. Da es ja mittlerweile auch Modularisierungstechniken von Frontend-Code inklusive Lazy Loading gibt, steht dem auch auf technischer Seite nichts im Weg. Wie wir in einer solchen SPA herausfinden, wie die Bounded Contexts geschnitten werden sollen, erfolgt genauso wie beim Backend durch ein gemeinsames Knowledge Crunching aller Projektbeteiligten unter Verwendung eines der etablierten Modellierungsverfahren.

Tactical Patterns

Die Tactical Patterns stellen Entwicklungsmuster bereit, die innerhalb eines Bounded Context zum Einsatz kommen. Im Folgenden gebe ich einen kurzen Überblick über die wichtigsten Themen im Zusammenhang mit Tactical Patterns und wie sie im Frontend umgesetzt werden können.

Die Domäne ist frei von technischem Code

Domain-Driven Design verfolgt den Grundsatz, dass die Domänenlogik technikfrei sein muss, damit der Code sich vollständig auf die Domänenlogik fokussieren kann. Das hilft einerseits beim Verstehen und der Wartung des Codes und erlaubt andererseits die leichtere Austauschbarkeit von technischem Code, insbesondere Libraries oder Frameworks von Drittanbietern. In diesem Zusammenhang wird oft davon gesprochen, dass die Domänenlogik im „Zentrum“ oder im „Herzen“ der Software sitzt, während der technische Code nach „außen“ geschoben wird. Architekturmuster wie Hexagonal Architecture (auch Ports and Adapters genannt) folgen ebenfalls diesem Bild. Die Interaktion erfolgt dabei nur von außen nach innen, d. h., technischer Code benutzt den Domänencode, aber der Domänencode darf technischen Code nicht direkt aufrufen, damit diese strikte Trennung erhalten bleibt.

Im Frontend ist diese Abgrenzung bei weitem nicht so scharf, da hier ja auch die Repräsentation von Daten integraler Bestandteil ist, und diese Repräsentation ist natürlich ein technischer Aspekt. Benutzt man außerdem eine Technologie, bei der Code und Repräsentation sehr eng gekoppelt sind, wie das zum Beispiel bei React mit JSX der Fall ist, ist eine hundertprozentige Trennung kaum möglich. Trotzdem kann man auch in einem solchen Umfeld viel erreichen, wenn man die Geschäftslogik nicht direkt in den Komponenten implementiert, sondern sie in eigene Funktionen (in separaten Dateien) auslagert, die dann in den Komponenten aufgerufen werden. Das macht übrigens auch das Testen deutlich einfacher.

Ein weiterer technischer Aspekt, den man in Frontends so gut wie immer findet, ist die Kommunikation mit dem Backend. Auch hier ist es wichtig, die Technik von der Fachlichkeit zu trennen, also die eigentliche Anfrage (also welche Technologie zum Einsatz kommt, welche Implementierung dafür verwendet wird etc.) inklusive der erforderlichen Konfiguration zu kapseln und vom Domänencode zu trennen.

Der Code ist fachlich gruppiert, nicht technisch

Gemäß DDD wird der Code so organisiert, dass fachlich zusammengehörender Code nah beieinander abgelegt wird. Das hat mehrere Vorteile: Zum einen lässt sich so rasch erfassen, wo man nach dem Code suchen muss, der ein bestimmtes Feature umsetzt, sodass man sich auch in fremden Codebasen relativ leicht zurechtfinden kann. Des Weiteren beschränkt eine derartige Organisation die Änderungen, die bei der Anpassung eines einzelnen Features entstehen, auf wenige Verzeichnisse und verhindert das sogenannte „Shotgun Surgery“-Antipattern (also wörtlich „Operation mit der Schrotflinte“), bei dem Änderungen für ein einzelnes Feature gleichmäßig über die gesamte Codebasis verteilt sind – ganz so, als hätte man mit einem Schrotgewehr auf den Code geschossen. Das hilft insbesondere, Merge-Konflikte zu vermeiden, wenn im Team gleichzeitig an verschiedenen Features gearbeitet wird. Auch im Frontend lässt sich eine solche fachliche Organisation des Codes leicht durchführen.

Entities, Value Objects etc.

In Domain-Driven Design gibt es verschiedene Muster, nach denen der Code für einzelne Objekte umgesetzt werden sollte. Die Häufigsten sind dabei Entities und Value Objects. Bei den Entities handelt es sich um Objekte, die einen Lebenszyklus haben, in dessen Verlauf sich die Daten der Entity ändern können. Trotzdem ist dieses Objekt zu jeder Zeit eindeutig über einen Identifikator erkennbar. Bei einer Person können sich z. B. Name, Adresse oder Geschlecht ändern, aber mit Hilfe eines Identifikators, wie z. B. einer Kunden- oder der Steuernummer, lässt sich immer zweifelsfrei das richtige Objekt zuordnen.

Im Gegensatz dazu haben Value Objects keinen separaten Identifikator, sie sind nur über die enthaltenen Daten identifizierbar. Sind die Daten anders, muss es sich um ein anderes Value Object handeln. Daraus abgeleitet findet die Umsetzung als unveränderliches, „immutable“ Objekt immer größere Verbreitung. Man kann diese Umsetzung im Frontend entweder durch Libraries erzwingen oder sich selbst so weit disziplinieren, dass die entsprechenden Objekte nicht mutiert werden. Auch Bibliotheken, wie z. B. Redux, setzen voraus, dass der Store immutable ist, dass bei Änderungen also jedes Mal ein neuer Store erzeugt wird. Es ist also durchaus sinnvoll, sich an dieses Entwicklungsmuster zu gewöhnen. Es hilft bei der Fehlervermeidung und erleichtert das Testen und sollte daher eingesetzt werden, wo immer es möglich ist.

Aggregate

Ein Aggregat in DDD bildet einen Workflow ab und kümmert sich um alles, was damit zusammenhängt. Es bildet eine Transaktionsgrenze (d. h., alle geänderten Daten werden gemeinsam persistiert) sowie eine Konsistenzgrenze (d. h., alle Daten innerhalb des Aggregats müssen zu jeder Zeit gemäß der Regeln der Geschäftslogik konsistent sein).

Üblicherweise enthält ein Bounded Context mehrere Aggregate, was zu einer Modularisierung des Codes führt.

Im Frontend sind die Nutzerinteraktionen naturgemäß viel kleinteiliger als in einem Backend-Aggregat, das mit einem Aufruf einen gesamten Workflow abarbeiten sollte. Daher werden wir im Frontend in den seltensten Fällen ein vollständiges Aggregat vorfinden.

Allerdings können wir uns auch hier die Erkenntnisse von DDD zunutze machen, indem wir die relevanten Aspekte von Aggregaten nachbilden. Denn auch im Frontend haben wir ja Workflows in Gestalt der verschiedenen Seiten der Applikation bzw. der verschiedenen Teilbereiche einer Seite, auch wenn diese bei der Implementierung in viele Einzelaktionen des Benutzers zerfallen.

Daher können wir in unseren „Frontend-Workflows“ den hierfür notwendigen Domänencode zusammenfassen (z. B. in einer entsprechend benannten Datei oder in einem Verzeichnis) und ihn als eine Einheit betrachten. Und auch im Frontend können wir die Konsistenz der Daten gemäß der Geschäftslogik durch geeignete Prüffunktionalität sicherstellen. Als Transaktionsgrenze kann zum Beispiel die Übertragung eingegebener Daten ins Backend angesehen werden. Auf diese Weise haben wir zwar nicht unmittelbar Aggregate implementiert, aber zumindest werden wichtige Eigenschaften der Aggregate nachgebildet.

Fazit

Auch wenn Domain-Driven Design normalerweise lediglich mit Blick auf das Backend betrachtet wird, lassen sich doch auch für das Frontend viele nützliche Entwicklungsmuster ableiten. So können Single Page Applications im Hinblick auf Verständlichkeit, Erweiterbarkeit und Wartbarkeit ähnlich gut umgesetzt werden wie reine Backend-Software.

Alle News zum Software Architecture Camp!