DDD einführen ohne Big Bang

Flache Editorial-Illustration: Eine leere Fußgängerbrücke verbindet eine dichte alte Stadt aus dunklen Navy-Gebäuden mit einer kleinen, halbfertigen neuen Siedlung im Baugerüst
Abbildung 1. Eine halbe neue Welt, eine ganze alte Welt — und eine Brücke, die niemand betreten will

Die meisten gescheiterten DDD-Einführungen, die ich gesehen habe, waren an der Fachlichkeit gar nicht das Problem. Das Problem war die Ambition. Ein Team liest Evans, erkennt sein System im Kapitel über Big Ball of Mud wieder und beschließt den großen Wurf: alles neu schneiden, alles umbenennen, diesmal richtig. Sechs Monate später gibt es eine halbe neue Welt, eine ganze alte Welt und eine Brücke dazwischen, die niemand betreten will.

Dabei widerspricht der Big Bang dem Kern von DDD. Domänenmodelle entstehen durch Lernen. Gelernt wird beim Umbauen selbst.

Entscheiden ja, umbauen nein

Die wichtigste Regel, die ich Teams mitgebe: Der Schnitt in Bounded Contexts wird entschieden und dokumentiert, aber nicht vorab umgesetzt. Kein Vorab-Refactoring. Die Entscheidung steht als Architecture Decision Record im Repository und tritt mit der ersten Story in Kraft, die einen der neuen Kontexte tatsächlich anfasst.

Das gilt auch für Namen. Wenn das Bestandssystem eine Klasse Applicant hat, die künftig Policyholder heißen soll, weil das Konzept dahinter in Wahrheit nicht der Antragsteller ist, sondern das laufende Vertragsverhältnis, dann wird sie erst umbenannt, wenn eine fachliche Änderung sie ohnehin berührt. Bis dahin steht der alte Name als dokumentierte Architekturschuld in der Liste, mit Datum und Abbaukriterium. So bleibt er sichtbar und dokumentiert. Er kostet trotzdem keinen eigenen Umbau.

Der Schnitt folgt der Sprache, nicht dem Lehrbuch

Woher kommt der richtige Schnitt? Aus der Sprache des Geschäfts, nicht aus dem Organigramm oder der Modulliste des Bestandssystems. Die findet man an den Stellen, wo dasselbe Wort plötzlich etwas anderes bedeutet.

Ein typisches Muster bei Portalen mit Mitgliedschaft oder Abo: Die Domäne zerfällt entlang des Kundenlebenszyklus in zwei Kernkontexte. Vor dem Vertragsschluss dreht sich alles um Anfrage, Beratung und Termin. Danach geht es um Konto, Nutzung und Bestand. Den Beleg für diese Grenze liefert oft schon das CRM: Ein Interessent ist dort ein „Lead", ein Kunde ein „Contact", zwei getrennte Entitäten mit getrennten Prozessen. Die Fachsprache hat den Schnitt in solchen Fällen längst vollzogen, die Software oft noch nicht.

Context Map: zwei Kernkontexte entlang des Kundenlebenszyklus, unterstützende Kontexte und ein externes CRM hinter Anticorruption Layern
Abbildung 2. Die Fachsprache hatte den Schnitt längst vollzogen: Context Map entlang des Kundenlebenszyklus

Teamgröße schlägt Orthodoxie

Bei einem Schnitt wie diesem liegt oft eine lehrbuchnähere Variante daneben: fast doppelt so viele Kontexte, sauber nach Geschäftsfähigkeiten geschnitten. Für ein kleines Team wäre das Kontext-Inflation: mehr Grenzen zu pflegen, mehr Übersetzungen, mehr Zeremonie, die niemand braucht.

Entschieden wurde der gröbere Schnitt, mit einem ausdrücklichen Split-Recht: Der große Kontext darf später innen weiter geteilt werden, wenn Team oder Fachlichkeit wachsen, solange seine Außengrenze stabil bleibt. Ein Bounded Context ist keine Ewigkeitsentscheidung. Er ist die beste Grenze für den aktuellen Stand des Wissens, revidierbar, wenn das Wissen wächst.

Zur gleichen Disziplin gehört der umgekehrte Fall: Ein UI-Unterschied ist nicht automatisch ein Kontext-Unterschied. Dass Besucher und Mitglieder verschiedene Ansichten sehen, ist eine Frage von Identität und Oberfläche. Eine UI-Sicht, die ins Backend leakt, erzeugt genau die Kopplung, die man mit DDD loswerden wollte.

Volles DDD nur, wo es sich lohnt

Aggregates, Value Objects, Domain Events: Das Instrumentarium hat seinen Preis, und er lohnt sich nur dort, wo die eigene Fachlogik lebt. Für fremde Daten aus Nachbarsystemen reicht eine Übersetzungsschicht, ein Anticorruption Layer, der die Fremdsprache des anderen Systems in das eigene Modell übersetzt. So schlank wie möglich, aber nicht schlanker: Je korrumpierender das Fremdmodell, desto mehr Übersetzungsarbeit gehört genau hierhin. Wer dagegen jede angebundene API mit Aggregate-Zeremonie versieht, baut Aufwand ohne Erkenntnisgewinn.

Grenzen halten, ohne groß umzubauen

Drei Praktiken sorgen dafür, dass der beschlossene Schnitt im Alltag hält, ohne dass je ein Umbauprojekt nötig wird:

Grenzen als Test. Die Regel „Kontext A greift nicht direkt auf Kontext B zu" steht als Architekturtest im Build, in PHP etwa mit Deptrac oder PHPat, in .NET und Java mit den ArchUnit-Varianten. Verletzt eine Änderung die Grenze, wird der Build rot, Wochen bevor daraus ein Gutachten-Finding würde.

Kommunikation in Phasen. Kontexte reden anfangs synchron miteinander, so banal wie möglich. Domain Events im selben Prozess kommen, wenn der erste echte Anwendungsfall sie braucht. Ein Message Broker kommt zuletzt, falls überhaupt. Wichtig ist nur, die Auslöser für den nächsten Schritt vorab zu notieren, damit die Entscheidung später nicht im Streit fällt.

Austauschbare Ränder. Jedes Nachbarsystem steht ab dem ersten Tag hinter einem Interface mit zwei Implementierungen: einer Attrappe für Entwicklung und Test und der echten Anbindung. Der Wechsel ist eine Zeile in der Konfiguration. So blockiert kein fremdes System das eigene Lernen.

Die Migration, die keine ist

Wenn der erste Kontext dann wirklich herausgelöst wird, ist es keine Operation am offenen Herzen, sondern eine Handvoll kleiner Schritte, jeder ein eigener Branch, jeder einzeln lieferbar, zusammen etwa ein Sprint. Das funktioniert, weil alle Entscheidungen längst getroffen und dokumentiert sind. Der Umbau ist nur noch Ausführung.

Der ehrliche Nachsatz dazu: So glatt läuft es nur, wenn keine Daten umziehen müssen. Bei einem neu herausgelösten Kontext ohne eigene Produktivdaten ist das oft der Fall. Sobald Kontexte sich Tabellen teilen, ist der Datenbesitz die eigentliche Arbeit: pro Tabelle ein Eigentümer-Kontext, Schreibzugriffe nur durch ihn, Lesezugriffe der anderen über die Übersetzungsschicht, und jede weiterhin geteilte Tabelle als dokumentierte Schuld mit Abbaukriterium. Das dauert länger als jeder Code-Umbau und gehört deshalb als Erstes auf den Tisch, nicht als Letztes.

DDD einführen heißt, eine Reihe kleiner, umkehrbarer Entscheidungen zu treffen und jede einzelne festzuhalten. Wie offen das Ende dabei bleiben darf, zeigt sich oft erst Jahre später: Notierte Auslöser für die nächste Ausbaustufe treten manchmal nie ein, und ein alter Name steht noch lange in der Schuldenliste, wartend auf die Story, die ihn berührt. Auch das ist ein Ergebnis.


Klingt nach deinem Thema? Schreib mir. Im Erstgespräch klären wir unverbindlich, ob und wie ich helfen kann.