
In den letzten drei Monaten habe ich mich (aufgrund meiner Bachelorarbeit) intensiv mit Domain-Driven Design in der Flutter-App-Entwicklung beschäftigt. Dabei habe ich viel gelernt und möchte die gewonnenen Erkenntnisse in einer Serie von Blog-Beiträgen teilen.
Dieser Beitrag bildet die Grundlage und befasst sich allgemein mit dem Thema Domain-Driven Design (DDD). Falls du mit den Konzepten von DDD bereits vertraut bist, kannst du diesen Beitrag überspringen und direkt zum nächsten Artikel über die konkrete Anwendung in Flutter übergehen.
In Softwareprojekten sind Entwicklungsteams früher oder später mit wachsender Komplexität konfrontiert. Wenn dieses Problem ignoriert wird, endet ein Projekt häufig im sogenannten Big Ball of Mud, einem schwer wartbaren und kaum testbaren “Spaghetti-Code”. Dieses Phänomen entsteht, wenn unterschiedliche Anforderungen der Software unstrukturiert miteinander vermischt werden. Unterschiedliche Anforderungen an Software sind beispielsweise:
Software, die ihre eigentlichen Ziele verfehlt, ist oft das Resultat einer fehlenden strategischen Vorgehensweise. Ein weit verbreitetes Problem ist die Misskommunikation zwischen den Teammitgliedern. Entwickler, Geschäftsleute und Designer haben oft unterschiedliche Perspektiven und Fachsprachen. Dieses Problem muss aktiv addressiert werden, um die Software langfristig erfolgreich zu machen.
Auch Flutter-Apps können komplex werden. Moderne Anforderungen wie Offline-First, also die Anforderung, dass eine App auch ohne stabile Internetverbindung zuverlässig funktioniert, machen aus einer Anzeige der Benutzeroberfläche eine komplexe Anwendung. Die Komplexität steigt zudem oft mit der Zeit weiter an: Neue Features kommen hinzu, bestehende müssen erweitert werden. Selten wird eine Anwendung einmal entwickelt und ist damit "fertig".
Domain-Driven Design adressiert genau diese Herausforderungen. Es bietet eine Sammlung von Methoden und Herangehensweisen, um Komplexität von Anfang an handhabbar zu machen.
DDD ist schon seit dem Jahr 2003 in der Softwareentwicklung ein Begriff. Dieser wurde erstmals von Eric Evans in seinem Buch: “Domain-Driven Design: Tackling Complexity in the Heart of Software” eingeführt. Obwohl die Ideen also seit Langem existieren, hat DDD erst in den letzten Jahren an Popularität gewonnen. Bevor ich in die Details eintauche, ist es zunächst wichtig folgendes klarzustellen: DDD ist mehr als eine Technik – es ist ein Paradigmenwechsel, eine neue Denkweise (jedenfalls war es das für mich).
Ursprünglich wurde DDD in zwei Bereiche unterteilt: Strategisches Design und Taktisches Design. Im Laufe der Jahre kamen aber weitere Konzepte hinzu. Ich teile DDD daher in strategisches Design, taktisches Design und kollaborative Modellierungsmethoden auf.

Die Ubiquitous Language ist das Fundament von DDD. Sie bezeichnet eine gemeinsame Sprache die innerhalb einer Domäne – also einem spezifischen Geschäftsbereich einer Organisation – von allen Beteiligten gesprochen wird.
Die gemeinsame Sprache ist entscheidend, um Missverständnisse in der Kommunikation zwischen Entwicklern, Fachexperten und anderen Beteiligten zu vermeiden. Um die Begriffe festzuhalten, empfiehlt es sich, ein Glossar zu führen, in dem die Begriffe der Ubiquitous Language definiert und festgehalten werden können. Wichtig ist, dass sich die Ubiquitous Language an der Fachlichkeit und nicht an technischen Details orientiert. Daher spielen die Geschäftsleute und Fachexperten eine wichtige Rolle in der Entwicklung der Ubiquitous Language.
Sie wird allen Teilen von DDD angewendet: In den Gesprächen zwischen Teammitgliedern, aber auch im Code selber zur Benennung von Klassen, Variablen etc.
Das strategische Design umfasst die übergeordnete Planung (Strategie) der Software. Dabei wird zwischen dem Problemraum (Analyse des Geschäfts) und dem Lösungsraum (Entwurf der Software-Struktur) unterschieden.
Untersuchung des Problemraums: Zunächst wird der Problemraum untersucht. Dabei wird die Domäne als Ganzes untersucht, um ihre die Teilbereiche, die Subdomänen, zu identifizieren. Diese werden anschließend in drei Kategorien eingeteilt:
Diese Kategorisierung hilft dabei, die Domäne detailliert zu verstehen und Prioritäten für die Entwicklung festzulegen.

Lösungen entwerfen im Lösungsraum: Nach der Analyse des Problemraums widmet sich das strategische Design dem Lösungsraum. Hier werden die sogenannten Bounded Contexts gebildet. Ein Bounded Context definiert eine klare Grenze, innerhalb derer ein bestimmtes Domänenmodell und eine spezifische Ubiquitous Language gelten.
In einer Microservice-Architektur entspricht ein Bounded Context oft einem einzelnen Microservice. Typischerweise wird ein Bounded Context von einem Team entwickelt. Ein Team kann auch mehrere Bounded Contexts verantworten, aber es wird nicht empfohlen, einen Bounded Context von mehreren Teams entwickeln zu lassen.
Der entscheidende Unterschied ist: Subdomänen werden im Problemraum entdeckt, Bounded Contexts werden im Lungsraum geschaffen. Eine 1:1-Beziehung zwischen einer Subdomäne und einem Bounded Context ist oft sinnvoll, aber nicht zwingend. Je nach Anforderung kann es auch sinnvoll sein, mehrere Bounded Contexts für eine Subdomäne zu entwerfen oder einen Bounded Context über zwei Subdomänen zu spannen. Diese architektonische Entscheidung sollte im Team gemeinsam mit Entwicklern, Fachexperten und Business-Analysten getroffen werden.
Um die Entscheidungen im strategischen Design bestmöglich treffen zu können und ein Verständnis über die Domäne aufzubauen, gibt es verschiedene kollaborative Methoden. Die bekannteste davon ist wahrscheinlich das Event Storming von Alberto Brandolini. Das Event Storming wird normalerweise als Workshop-Format durchgeführt und dient dazu, ein gemeinsames Verständnis im Team über die Domäne aufzubauen.
Es gibt mehrere Formate des Event Stormings: das Big Picture Event Storming sowie das Detail Event Storming.
Big Picture Event Storming: Dieses Format wird idealerweise zu Beginn eines Projekts mit einem breiten Team (Entwickler, Designer, Fachexperten etc.) durchgeführt, um ein gemeinsames Verständnis für die gesamte Domäne zu schaffen. Der Prozess umfasst mehrere Schritte:
Das Ergebnis eines Big Picture Event Stormings ist eine gute Grundlage, um Subdomänen zu identifizieren und zu kategorisieren.
Detail Event Storming: Nachdem die Subdomänen im Big Picture Event Storming identifiziert wurden, kann für jede ein Detail Event Storming durchgeführt werden. Dabei arbeitet ein kleinerer Kreis (hauptsächlich Entwicker und auch Fachexperten) daran, eine Subdomäne im Detail zu untersuchen. Zusätzlich zu den Events werden weitere Elemente erfasst:
Mit den detaillierten Untersuchungen jeder Subdomain lässt sich die Entscheidung treffen, welche Bounded Context man haben möchte. Zudem liefern die Ergebnisse des Detail Event Stormings erste Anhaltspunkte für die Objekte des taktischen Designs.
Das taktische Design befasst sich mit der konkreten Umsetzung des Domänenmodells im Code. DDD ist architektur-agnostisch, es wird also keine spezifische Architektur vorgeschrieben. Die DDD-Literatur empfiehlt aber Muster, die die Komplexität der Domäne handhaben können.
Architektur-Muster: Evans stellt in seinem Buch eine Schichtenarchitektur mit vier Schichten vor: Präsentationsschicht, Applikationsschicht, Domänenschicht und Infrastrukturschicht. In einer Schichtenarchitektur ist jede Schicht von der darunterliegenden Schicht abhängig. Ein zentrales Ziel von DDD ist die Isolation der Domänenschicht. In einer klassischen Schichtenarchitektur wäre die Domänenschicht jedoch von der Infrastrukturschicht abhängig, da diese unterhalb der Domänenschicht liegt. Um dieses Problem zu lösen, wird das Dependency Inversion Principle angewandt: Die Domänenschicht definiert Interfaces (z.B. für ein Repository), während die Infrastrukturschicht die konkreten Implementierungen übernimmt. Dadurch bleibt die Domänenschicht unabhängig von technischen Details und das Ziel der Isolation der Domänenlogik wird erfüllt.

Für komplexe Bounded Contexts wird in der DDD-Literatur andere Muster vorgestellt. Ein Beispiel ist CQRS (Command Query Responsibility Segregation). Bei diesem Muster wird die Verantwortung für das Schreiben von Daten (Commands) von der Verantwortung für das Lesen von Daten (Queries) getrennt. Die Domänenlogik existiert nur auf der Schreib-Seite. Die Lese-Seite ist darauf optimiert, Daten effizient für die Benutzeroberfläche bereitzustellen. Dafür werden auf der Lese- und auf der Schreibe-Seite separate Datenbanken verwendet, die im Hintergrund synchronisiert werden. CQRS löst damit das Problem, dass Anforderungen der Benutzeroberfläche das Domänenmodell unnötig verkomplizieren können.

Die Architektur ist allerdings natürlich komplexer und ressourcenaufwändiger. Daher sollte es eingesetzt werden, wenn ein Bounded Context besonders komplexe Anforderungen hat.
Die Bausteine des Domänenmodells: Das eigentliche taktische Design umfasst eine Reihe von Bausteinen zur Implementierung der Domänenlogik. Diese Bausteine liegen in der Domänenschicht der Anwendung:
Der Kerngedanke von Domain-Driven Design ist, den Fokus der Softwareentwicklung auf die Geschäftslogik zu legen. Es erfordert ein strategisches Vorgehen und erhebliche Vorarbeit, bevor die erste Zeile Code geschrieben wird. So wird die ansteigende Komplexität der Anwendung handhabbar gemacht und ein Big Ball of Mud vermieden. Dies führt zu einer gut wartbaren und testbaren Software, die auch langfristig weiterentwickelt werden kan. DDD ist besonders relevant für große, komplexe Anwendungen, die in Teams entwickelt werden.
DDD ist vor allem in der heutigen Zeit, in der große Teile des Codes von KI generiert werden kann, relevanter denn je. Der Fokus verschiebt sich weg von der reinen Beherrschung einer Technologie hin zur Fähigkeit, strategische Entscheidungen zu treffen und die Weiterentwicklung einer Anwendung im Team nachhaltig zu gestalten.
Im Part 2 zeige ich, wie DDD praktisch anhand einer Flutter-Anwendung angewandt werden kann.