Domain-driven Design erklärt

Know-how  –  1 Kommentare

Softwareentwicklung scheitert häufig nicht an der Technik, sondern an interdisziplinärer Kommunikation. Da Entwickler und Fachleute mit unterschiedlichen Terminologien arbeiten, gibt es Verständnisprobleme.

Einer der faszinierendsten Aspekte der Softwareentwicklung ist der stetige Wandel. Daher gehört die regelmäßige Weiterbildung für viele Entwickler zum Alltag. Thematisch geht es dabei nicht nur um Techniken, sondern immer häufiger auch um Konzept- und Methodenwissen, Soft Skills und gegebenenfalls Führungsqualitäten. Einzig um das domänenspezifische Fachwissen ist es zumeist schlecht bestellt.

Entwicklern kann man deshalb allerdings vermutlich kaum etwas vorwerfen. Das hohe Tempo der Branche bedingt bereits eine enorme Spezialisierung in puncto Technik, beispielsweise im Hinblick auf Sprachen, Frameworks und Plattformen. Da viele Unternehmen die IT zudem fälschlicherweise nicht als strategisch wichtig ansehen, ist ein Wechsel des Arbeitsplatzes in der Regel mit einer neuen Fachdomäne gleichbedeutend. Häufig kommt daher die Frage auf, wozu das mühsame Einarbeiten in ein Thema nützt, das vermeintlich nicht von primärer Bedeutung für die eigene Arbeit ist und ohnehin in einigen Jahren hinfällig sein wird.

Softwareentwicklung ist kein Selbstzweck

Viele vergessen über all dem, dass Softwareentwicklung kein Selbstzweck ist, sondern in der Regel lediglich dazu dient, ein fachliches Problem zu lösen. Einer der zentralen Aspekte für eine fachlich korrekte Implementierung ist dabei das Verständnis der Thematik und der dazugehörigen Fachsprache. Genau die ist häufig allerdings nicht eindeutig definiert. Das führt in interdisziplinären Teams zu Kommunikationsproblemen, was in Verständnisschwierigkeiten zwischen Entwicklern und Fachabteilung und letztlich unnötigem Entwicklungsaufwand und Frust enden kann.

Wie schwierig es ist, unterschiedliche Sprachen zu vereinen, zeigt das Beispiel der Industrie 4.0. Während die Problemsprache häufig industriegetrieben ist, bewegt sich die Sprache der zugehörigen Lösung im digitalen Raum. Zahlreiche Unternehmen, die in der realen Welt mit klassischen Geschäftsmodellen ausgesprochen erfolgreich sind, gelingt die digitale Transformation deshalb entweder gar nicht oder nur äußerst schwerfällig.

Es wäre wünschenswert, die sprachliche Kluft zwischen Entwicklern und den fachlich getriebenen Domänenexperten zu überwinden, um sich gemeinsam auf die eigentliche Aufgabe konzentrieren zu können. Das Ergebnis könnte qualitativ bessere Software sein, die in kürzerer Zeit mit weniger Fehlern entwickelt wird und sowohl Bedürfnisse als auch Anforderungen der Anwender passender abbildet.

Intention statt Implementierung

Der entscheidende Faktor zum Erreichen des Ziels ist, die Intention über die Implementierung zu stellen. Die primäre Fragestellung darf nicht lauten, wie sich Geschäftsprozesse durch Technik abbilden lassen. Stattdessen sind die Absichten und Ziele des Anwenders in den Mittelpunkt zu rücken. Das gestaltet die Anforderungen für die Entwickler greifbarer und verständlicher, da für sie so ein Sinn zu erkennen ist.

Doch nicht nur die Entwicklung könnte von einem fachlich getriebenen Vorgehen profitieren, auch für andere Aspekte würden sich Vorteile ergeben. Dazu zählen beispielsweise das Durchführen von Funktionstests, die fachliche Abnahme und die Verbesserung der Wartbarkeit.

Um das Ziel zu erreichen, gilt es, die für eine Software relevante Fachsprache von vornherein interdisziplinär zu erarbeiten. Dazu bietet sich die Analyse von Geschäftsprozessen an, weil sie die Intention und den Zweck einer Fachdomäne ausdrücken. Das gilt vor allem im Vergleich zu einer Erhebung des rein statischen Datenmodells. Der Kern der Fachsprache besteht daher primär aus Verben, nicht aus Substantiven.

Semantische Ereignisse als Fundament

Jede Fachdomäne lebt davon, dass Ereignisse eintreten und Reaktionen auslösen. Ein Ereignis könnte beispielsweise der Umzug eines Kunden sein, die Reaktion wäre das Einrichten eines Nachsendeauftrags. Um das Beispiel erfolgreich implementieren zu können, sind die Adressdaten zwar erforderlich, für das Verständnis wichtiger ist aber die Intention: Warum haben sich die Adressdaten geändert? Ist der Kunde wirklich umgezogen? Oder wurde nur ein Schreibfehler in den Stammdaten korrigiert? Eventuell wurde auch die Straße umbenannt oder die Postleitzahl neu vergeben?

Die genannten Fälle stellen unterschiedliche Ereignisse dar, auf die entsprechend zu reagieren ist. Daher müssen die Ereignisse semantisch angereichert sein, um die Intention erkennen zu können. Eine Software, die lediglich die Änderung der Adresse feststellt, ist nicht in der Lage, den Situationen in angemessener Form zu begegnen. Daher steht am Beginn einer Entwicklung das gemeinsame Modellieren der Geschäftsprozesse mit semantischen Ereignissen als Fundament. Dabei entstehen Ereignisströme, die sich unter anderem historisch auswerten und analysieren lassen, beispielsweise im Rahmen von Big-Data-Aufgaben und Deep-Learning-Prozessen.

Modellierung

Domain-driven Design

Für die interdisziplinäre Modellierung ist ein einheitliches und standardisiertes Vorgehen hilfreich, das Entwickler und Fachexperten integriert. Der Ansatz des Domain-driven Design (DDD) verfolgt dieses Ziel und taucht häufig als Bestandteil von Command Query Responsibility Segregation (CQRS) und Event Sourcing auf. Da DDD unabhängig von beiden Methoden ist, greift das allerdings zu kurz. Die gelegentlich genannte Gleichung, dass CQRS der Verbund aus Command Query Separation (CQS), Event Sourcing und DDD sei, ist daher falsch.

Das von Bertrand Meyer erdachte CQS-Entwurfsmuster beschreibt in der objektorientierten Programmierung das strikte Zuordnen von Methoden zu zwei Kategorien: Kommandos ändern den Zustand des Objekts, geben aber keine Daten zurück; Abfragen liefern Daten zurück, modifizieren aber den Zustand des Objekts nicht. Auf der Basis von CQS hat Greg Young das Entwurfsmuster CQRS definiert, das die Anwendung von CQS auf Architekturebene beschreibt.

Event Sourcing hingegen ist ein Verfahren zum Persistieren des Zustands einer Anwendung. Im Gegensatz zu anderen Vorgehensweisen speichert das Programm dabei nicht den aktuellen Zustand, sondern den Weg dorthin in Form von Ereignissen. Letztere stellen jeweils eine Veränderung dar und ergeben in ihrer Gesamtheit den aktuellen Zustand. Event Sourcing ähnelt daher einem Sparbuch, in dem eine Bank ebenfalls nicht den aktuellen Saldo festhält, sondern eine Historie von Transaktionen, aus der sich (unter anderem) der aktuelle Saldo ermitteln lässt.

Da die Abfolge von Ereignissen die Realität besser beschreibt als die rein strukturelle Repräsentation der Gegenwart, passen DDD und Event Sourcing gut zusammen. Außerdem eignen sich Ereignisse zum semantischen Abbilden von Geschäftsprozessen. Ähnlich verhält es sich mit DDD und CQRS, weil die Kommandos von letzterem die sprachliche Genauigkeit und Ausdrücklichkeit unterstützen, die DDD fordert. Dass CQRS und Event Sourcing häufig in den Vordergrund rücken, liegt an deren Greifbarkeit: Es sind reine Technologiethemen, die Entwickler häufig stärker ansprechen. Zumindest aus ihrer Sicht lassen sie sich einfacher diskutieren als die eigentliche Fachlichkeit.

Eine gemeinsame Sprache

Die Grundlage für die gemeinsame und interdisziplinäre Modellierung bildet die Idee, Sprache zu formalisieren. Allerdings handelt es sich bei dem Ergebnis nicht um eine Universalsprache, die für jede Situation geeignet ist. Ein derartiges Konstrukt wäre unscharf – Spezialsprachen hingegen sind exakt. Es lässt sich daher festhalten, dass eine Sprache in der Regel kontextabhängig ist.

Für ein einzelnes Unternehmen kann beispielsweise eine Person gleichzeitig unterschiedliche Rollen innehaben. So kann ein Kunde zum Beispiel nicht nur der Adressat einer Warensendung oder der Nutznießer einer Dienstleistung sein, sondern zugleich auch ein Schuldner oder ein Reklamant. Während die Rolle des Adressaten für den Versand relevant ist, interessiert die Buchhaltung hingegen die Rolle des Schuldners. Beide Abteilung treten zwar mit der gleichen Person in Kontakt, interagieren aber auf unterschiedliche Art in ihrer jeweils eigenen Sprache.

Geschäftsprozesse modellieren

Bei alldem spielen jedoch nicht nur die einzelnen Abteilungen eine Rolle. Auch das Konzept "Kunde" lässt sich mit unterschiedlichen Begriffen belegen. Bei ihnen muss man sich überlegen, ob sie synonym zueinander sind oder eventuell unterschiedliche Dinge beschreiben: Meint das Wort "Kunde" das Gleiche wie "Anwender" oder "Benutzer"? Es mag Szenarien geben, in denen der Einkäufer zugleich auch Schuldner und Anwender ist. In anderen Szenarien sind die Rollen hingegen auf mehrere Personen verteilt. Auch in dem Fall ist der Kontext entscheidend.

DDD bezeichnet die gewählte Sprache als "Ubiquitous Language". Von Zeit zu Zeit begegnet man alternativen Formulierungen, die zumeist mit dem Ziel eingeführt wurden, den im Deutschen unhandlichen Begriff "Ubiquitous" durch etwas Greifbareres zu ersetzen. Zu den bekannten Varianten zählen beispielsweise "Universal Language" oder "Common Language". Die Befürworter solcher Begriffe begehen jedoch den Fehler, die von DDD geforderte Eindeutigkeit bei DDD zu verwässern.

Eine Domäne lässt sich in Kontexte unterteilen, die den Gültigkeitsraum der jeweiligen Ubiquitous Language darstellen. Zwischen Kontexten ist eine Übersetzung erforderlich. Dazu dient das sogenannte Context Mapping.

Ereignis und Intention

Innerhalb eines Kontexts beschreibt die darin gültige Sprache vor allem Ereignisse, zum Beispiel "ein Kunde ist umgezogen" oder "ein Schuldner hat eine offene Rechnung beglichen". Die Ereignisse können durch Interaktionen eines Anwenders mit Software entstehen, sie können aber auch andere Auslöser haben: Die Bandbreite reicht von Ereignissen in der realen Welt bis zu Abhängigkeiten von der Zeit.

Falls Ereignisse aus der Interaktion mit Software entstehen, beispielsweise indem der Anwender eine Schaltfläche betätigt, gibt es zwei Abstraktionsebenen für das Ereignis. Zum einen die des rein technischen Ereignisses – es besagt, dass eine Schaltfläche betätigt wurde. Zum anderen gibt es die Intention des Anwenders, also warum er die Schaltfläche betätigt hat. Die Fachdomäne interessiert Zweiteres.

Allerdings führt nicht jede Intention zum angestrebten Ziel. Ein Warenhaus könnte zum Beispiel einschränken wollen, wie viele Artikel ein Einkäufer in den Warenkorb legen darf, abhängig von seiner Bonität oder einem ähnlichen Scoring. Das Kommando leg den Artikel in den Warenkorb würde in dem Fall nicht unbedingt das Ereignis ein Artikel wurde in den Warenkorb gelegt auslösen.

Im Vergleich dazu ist ein von einem Sensor gemessener Temperaturwert ein Ereignis, über dessen Zulässigkeit nicht mehr entschieden werden kann: Die Messung ist abgeschlossen, daran lässt sich nichts mehr ändern. Ein solches Ereignis stellt allerdings auch keine Intention dar, sondern es enthält lediglich Rohdaten. Daher stellt sich durchaus die Frage, inwiefern das Ereignis an sich domänenrelevant ist oder ob es eines Kommandos bedarf, mit dem sich Schlüsse aus dem Ereignis ziehen und sie in eine Intention umwandeln lassen.

Aggregates

Aggregates als Konsistenzgrenzen

In beiden Fällen muss irgendetwas das Kommando entgegennehmen und prüfen, ob es zulässig ist. Nur dann darf daraus anschließend das passende Ereignis erzeugt werden.

Die Aufgabe übernimmt ein weiteres Konstrukt von DDD, das sogenannte Aggregate. Dabei handelt es sich um ein Objekt, das Methoden zum Verarbeiten der Kommandos enthält und Ereignisse auslöst. Intern darf ein Aggregate zudem Daten zu einem Geschäftsvorgang speichern, es verfügt folglich über einen Zustand. Er ist erforderlich, um Geschäftsprozesse abbilden zu können, die im Lauf der Zeit mehr als ein Kommando umfassen.

Wichtig für die Definition der Aggregates ist, dass sie eine Konsistenzgrenze darstellen. Das Objekt muss garantieren, dass sein Zustand jederzeit konsistent ist – vor einem Kommando ebenso wie danach. Die Anforderung besteht allerdings lediglich für jedes Objekt individuell, nicht übergreifend für alle oder zumindest unterschiedliche Objekte. Rein technisch stellt ein Aggregate somit eine Transaktionsgrenze dar. Transaktionen dienen dabei aber nur als Mittel, um Invarianten und Konsistenz zu garantieren.

An der Stelle zeigt sich deutlich, warum DDD und CQRS beziehungsweise DDD und Event Sourcing so gut zueinander passen. Will man die Beziehung der Konzepte auf zwei einfache Formeln reduzieren, ließe sich sagen:

DDD + Event Sourcing = Ereignisse
DDD + CQRS = Kommandos

Identität und Gleichheit

Rein technisch ist die Aussage, ein Aggregate ist ein Objekt, nicht ganz korrekt. Genau genommen kann es sich um ein einzelnes Objekt handeln, es kann aber auch ein ganzer Verbund sein. In dem Fall muss das Aggregate die Konsistenz nicht nur für ein Objekt garantieren, sondern für den gesamten Verbund.

Ein solcher enthält meist zwei Arten von Objekten: Entities und Value Objects. Deren primäres Unterscheidungsmerkmal ist die Frage, ob sie über eine eigene Identität verfügen oder nicht. Entities besitzen eine solche und sind daher per Definition selbst dann unterschiedlich, wenn sie den gleichen Zustand kapseln. Value Objects verfügen hingegen nicht über eine Identität und gelten daher als gleich, wenn sie die gleichen Werte enthalten. Sie sind daher häufig als unveränderliche Konstrukte realisiert.

Nicht alle Programmiersprachen unterstützen das Konzept der Unveränderlichkeit nativ, weshalb Entwickler es im Zweifelsfall nachbilden müssen. Dazu genügt es, auf Methoden zu verzichten, die den Zustand des zugehörigen Objekts verändern. Stattdessen geben sie eine neue Instanz zurück, wie das folgende, in JavaScript geschriebene Beispiel zeigt:

'use strict';

const Probability = function (probability) {
if ((probability < 0) || (probability > 1)) {
throw new Error('Invalid probability.');
}

this.value = probability;

this.equals = function (other) {
const epsilon = 0.01;

return Math.abs(this.value - other.value) < epsilon;
};

this.inverse = function () {
return new Probability(1 - this.value);
};

this.combined = function (other) {
return new Probability(this.value * other.value);
};

this.either = function (other) {
return new Probability(
(this.value + other.value) - (this.value * other.value)
);
};
};

module.exports = Probability;

Dass es sich dabei um ein Value Object handelt, lässt sich leicht an der Funktion equals erkennen. Sie erkennt die zwei Instanzen dann als gleich, wenn sie den gleichen Wert aufweisen.

Den Zugriff auf Aggregates kontrollieren

Falls ein Aggregate ein Verbund aus mehreren Entities und Value Objects ist, stellt sich die Frage, wie sich die Konsistenz innerhalb des gesamten Verbunds sicherstellen lässt. Dazu legt man ein Entity innerhalb des Aggregates als sogenannte Aggregate Root fest. Sie ist sozusagen das primäre Objekt des Verbunds. Rein technisch ist die Aggregate Root ein Entity-Objekt wie jedes andere. Ihre herausragende Stellung bezieht sie aus der Tatsache, dass sie die einzige Schnittstelle nach Außen darstellt.

Der direkte Zugriff auf die Entities und Value Objects innerhalb eines Aggregates ist verboten und darf ausschließlich über die Aggregate Root erfolgen. Ihr obliegt die Aufgabe, die anderen Objekte derart zu verändern, dass am Ende wieder ein konsistenter Zustand vorliegt.

Fazit

Es fällt auf, dass sich DDD auf sich selbst beziehen lässt. Es definiert für einen bestimmten Kontext in einer bestimmten Domäne eine spezifische, aber allgemeingültige Sprache, die Begriffe wie Domäne, Kontext, Aggregate, Kommando und Ereignis enthält. Aus einem gewissen Blickwinkel betrachtet, ist DDD also eine Metasprache, mit der sich andere Sprachen entwickeln lassen. Genau das ist letztlich der Kern von DDD: Es geht darum, geschäftliche Zusammenhänge exakt zu analysieren, eine passende Sprache zu entwickeln und mit ihrer Hilfe die Fachlichkeit in Programmcode abzubilden.

Es ist ausgesprochen hilfreich, zur Modellierung von Geschäftsprozessen eine Metasprache zur Hand zu haben. Allerdings muss man sich bewusst sein, dass der Aufwand für DDD hoch ist und sich das Verfahren daher nicht für jede Aufgabe eignet. Es empfiehlt sich allerdings darauf zurückzugreifen, wenn Probleme eine hohe Komplexität und einen hohen Geschäftswert aufweisen. In einem derartigen Kontext kann DDD seine Stärken voll ausspielen und führt am Ende zu qualitativ besserer Software, die in kürzerer Zeit mit weniger Fehlern entwickelt wird und die die Bedürfnisse und Anforderungen der Anwender besser abbildet.

Golo Roden
ist Gründer, CTO und Geschäftsführer der the native web GmbH, eines auf native Webtechnologien spezialisierten Unternehmens. Für die Entwicklung moderner Webanwendungen bevorzugt er JavaScript und Node.js und hat mit "Node.js & Co." das erste deutschsprachige Buch zum Thema geschrieben.