Softwarearchitektur verstehen und entscheiden

Aaron KreisÜber mich
Domänenarchitektur

Domain Driven Design

Software am Fachmodell ausrichten

Die Idee und ihr Nutzen

Die Schadenabteilung einer Versicherung spricht von Deckung, Selbstbeteiligung und Regulierung, also der Bearbeitung und Bezahlung eines Schadens. Die Schadenabteilung arbeitet mit einem Programm, der Schadenanwendung. In der Schadenanwendung stehen für dieselben Dinge Codewerte, also Zahlen, deren Bedeutung man nachschlagen muss: Ein regulierter Schaden steht zum Beispiel als status = 4 im Code. Bei jedem Änderungswunsch der Schadenabteilung müssen die Entwickler deshalb zwischen „reguliert“ und status = 4 übersetzen, und dabei entstehen Missverständnisse. Stattdessen klären Fachleute und Entwickler gemeinsam, was die Begriffe und Regeln genau bedeuten, und verwenden genau diese Begriffe auch im Programmcode, etwa im Aufruf reguliere(betrag). Fachleute und Entwickler teilen große Fachgebiete in Bereiche auf, damit ein Wort innerhalb eines Bereichs immer dasselbe bedeutet. So bedeutet „Zahlung“ im Bereich Regulierung eine Auszahlung an den Kunden und im Bereich Rückforderung einen Betrag, den der Verursacher eines Schadens an die Versicherung zahlt. Fachliche Regeln stehen dann in Aufrufen, deren Namen auch die Fachleute verstehen, etwa die Regel zur Selbstbeteiligung bei Glasschäden in Schaden.berechneSelbstbeteiligung(). Diese Arbeitsweise heißt Domain Driven Design, kurz DDD, auf Deutsch am Fachgebiet ausgerichteter Entwurf: Die Begriffe und Regeln der Schadenabteilung bestimmen, wie die Teile des Codes heißen und was sie tun.

Bausteine: Schadenabteilung: kennt die Fachsprache; Entwicklungsteam: schreibt den Programmcode; Gemeinsame Begriffe: gemeldet, geprüft, reguliert, Deckungssumme; Programmcode: Namen von Bausteinen und Aufrufen. Verbindungen: Schadenabteilung — Gemeinsame Begriffe; Entwicklungsteam — Gemeinsame Begriffe; Gemeinsame Begriffe — Programmcode (unverändert übernommen). Schadenabteilung und Entwicklungsteam legen die Begriffe gemeinsam fest, und der Programmcode verwendet sie unverändert.

Wie funktioniert es?

Das Entwicklungsteam der Schadenanwendung setzt sich in mehreren Arbeitstreffen mit der Schadenabteilung zusammen. Dabei entstehen gemeinsame Begriffe: Ein Schaden wird „gemeldet“, „geprüft“ und „reguliert“, und die „Deckungssumme“ ist der Höchstbetrag, den der Vertrag für diesen Schaden zahlt. Eine Zahlung gehört immer zu genau einem Schaden. Das Entwicklungsteam gibt dem Baustein Schaden die Aufrufe pruefeDeckung() und reguliere(betrag), benannt mit genau diesen Begriffen. Der Baustein Schaden steht im Code für einen Schaden mit allen seinen Zahlungen. Eine solche Gruppe fachlich zusammengehöriger Objekte heißt Aggregate. Alle Änderungen an der Gruppe laufen über ein einziges Objekt der Gruppe, das Aggregate Root. Beim Schaden ist der Baustein Schaden selbst das Aggregate Root, und die Zahlungen gehören zur Gruppe. Eine Sachbearbeiterin will für Schaden 4711 eine Zahlung über 2.000 Euro anlegen. Sachbearbeiter legen Zahlungen mit einem Programm an, dem Regulierungsdienst. Der Regulierungsdienst lädt den Schaden 4711 über das SchadenRepository. Das SchadenRepository lädt und speichert Schäden mit ihren Zahlungen. Der Regulierungsdienst erhält den Baustein Schaden mit allen Zahlungen, also das ganze Aggregate. Die neue Zahlung läuft nur über reguliere(2.000 €). Im Code gibt es keinen anderen Aufruf, mit dem sich eine Zahlung am Baustein Schaden vorbei anlegen lässt. Der Baustein Schaden prüft selbst, ob die Summe der Zahlungen dann noch innerhalb der Deckungssumme liegt. Erst danach speichert der Regulierungsdienst den Schaden über das SchadenRepository. Ein Jahr später fragt die Schadenabteilung, wo die Regel zur Selbstbeteiligung bei Glasschäden umgesetzt ist. Das Entwicklungsteam kann antworten, dass die Regel nur in Schaden.berechneSelbstbeteiligung() steht. Soll die Regel geändert werden, ändert das Entwicklungsteam nur diesen einen Aufruf.

Bei Domain Driven Design bestimmt das Fachgebiet, die Domäne, wie der Code aufgebaut ist; bei der Versicherung ist die Domäne die Schadenbearbeitung. Die Arbeitsweise hat zwei Teile. Das strategische Design gliedert das gesamte Fachgebiet:

  • Gemeinsame Sprache (englisch Ubiquitous Language): Fachleute und Entwickler einigen sich auf Begriffe und deren genaue Bedeutung. Diese Begriffe verwenden sie im Gespräch, in Dokumenten und im Code.
  • Abgegrenzte Bereiche (englisch Bounded Contexts): Die gemeinsame Sprache gilt innerhalb eines abgegrenzten Bereichs. Ein großes Fachgebiet wird in solche Bereiche geteilt, in denen jeder Begriff genau eine Bedeutung hat. Bei der Versicherung sind das zum Beispiel Schadenmeldung, Regulierung und Rückforderung. Im Bereich Rückforderung fordert die Versicherung gezahlte Beträge beim Verursacher eines Schadens zurück. In der Rückforderung haben manche Wörter der Regulierung eine andere Bedeutung.
  • Übersicht der Bereiche (englisch Context Map): Die Übersicht beschreibt, wie die Bereiche voneinander abhängen und wo zwischen ihnen übersetzt wird. Sie hält zum Beispiel fest, dass die Rückforderung die gezahlten Beträge aus der Regulierung übernimmt und dabei den Begriff „Zahlung“ übersetzt.

Das taktische Design liefert Bausteine für den Code innerhalb eines Bereichs:

  • Entity: ein fachliches Objekt mit eigener, dauerhafter Identität. Ein Schaden mit seiner Schadennummer ist eine Entity.
  • Value Object: ein Wert ohne eigene Identität, der nur durch seinen Inhalt bestimmt ist. Ein Geldbetrag mit Währung ist ein Value Object.
  • Aggregate: die Gruppe aus Aggregate Root und zugehörigen Objekten. Das Aggregate Root prüft bei jeder Änderung die Regeln, die die ganze Gruppe betreffen, beim Schaden etwa die Deckungssumme.
  • Domain Event: ein fachliches Ereignis, das für andere Programmteile wichtig sein kann. „Schaden reguliert“ ist ein Domain Event.
  • Repository: ein Baustein, der Aggregates lädt und speichert, ohne dass die Fachlogik die Datenbank kennen muss. Für Schäden ist das das SchadenRepository.

Welche Regeln gelten, etwa dass bei Glasschäden die Selbstbeteiligung entfällt, wissen die Fachleute der Schadenabteilung; den Code schreibt das Entwicklungsteam. Beide Gruppen gehören zu verschiedenen Abteilungen und arbeiten nur zusammen, wenn dafür Zeit eingeplant wird. Die Schadenabteilung bestimmt, was die Begriffe und Regeln bedeuten. Das Entwicklungsteam übernimmt die Begriffe und Regeln unverändert in den Code. Domain Driven Design ist eine Arbeitsweise und kein Produkt.

Übersteigt eine Zahlung die Deckungssumme, lehnt der Baustein Schaden die Zahlung ab, und der Regulierungsdienst speichert nichts. Der Regulierungsdienst meldet dann der Sachbearbeiterin den Grund. Ändert die Schadenabteilung später eine Regel, klären Schadenabteilung und Entwicklungsteam zuerst wieder die Bedeutung, bevor das Entwicklungsteam den Code ändert.

Domain Driven Design: die Zahlungsregel steht im Schaden selbst

Beteiligte: Schadenabteilung: kennt die Fachsprache; Entwicklungsteam: übernimmt die Begriffe in den Code; Regulierungsdienst: möchte eine Zahlung anlegen; Baustein Schaden: Aggregate: prüft bei jeder Zahlung die Deckungssumme; SchadenRepository: lädt und speichert den Schaden mit seinen Zahlungen. Ablauf: 1. In Arbeitstreffen von Schadenabteilung und Entwicklungsteam entstehen die gemeinsamen Begriffe „gemeldet“, „geprüft“, „reguliert“ und „Deckungssumme“. Eine Zahlung gehört immer zu genau einem Schaden. 2. Das Entwicklungsteam gibt dem Baustein Schaden die Aufrufe pruefeDeckung() und reguliere(betrag), benannt mit genau diesen Begriffen. 3. Der Regulierungsdienst lädt über das SchadenRepository den Schaden 4711 mit seinen Zahlungen. 4. Der Regulierungsdienst erhält den Schaden mit allen Zahlungen, also das ganze Aggregate. 5. Die neue Zahlung über 2.000 Euro läuft nur über reguliere(2.000 €). Im Code gibt es keinen anderen Aufruf, mit dem sich eine Zahlung am Baustein Schaden vorbei anlegen lässt. 6. Der Baustein Schaden prüft selbst, ob die Summe der Zahlungen dann noch innerhalb der Deckungssumme liegt. 7. Erst danach speichert der Regulierungsdienst den Schaden über das SchadenRepository. Fragt die Schadenabteilung später nach einer Regel, zeigt das Entwicklungsteam genau einen Aufruf im Baustein Schaden.

Wann ist es sinnvoll und wann nicht?

Domain Driven Design lohnt sich

  • Ein fachlich anspruchsvolles System mit vielen Regeln wird lange betrieben und weiterentwickelt. Die Schadenanwendung hat viele Regeln zu Deckung, Selbstbeteiligung und Zahlungen und wird über viele Jahre weiterentwickelt. In dieser Zeit kommen neue Entwickler dazu. Ohne Domain Driven Design müssen sie die Bedeutung von Codewerten wie status = 4 erst nachschlagen oder erfragen. Mit Domain Driven Design lesen sie im Code dieselben Begriffe, die die Schadenabteilung verwendet.
  • Fachabteilung und Entwickler verstehen sich bei Änderungswünschen häufig falsch. Die Fachleute der Schadenabteilung sprechen von „Schaden“, „reguliert“ und „Deckung prüfen“, im Code heißen dieselben Dinge ClaimEntity, status = 4 und processRecord(). Wer eine Frage der Schadenabteilung beantworten will, muss den Code erst zurückübersetzen, und bei jeder Übersetzung geht etwas verloren.
  • Niemand kann sicher sagen, wo eine fachliche Regel im Code steht. Ohne Domain Driven Design sind die fachlichen Regeln oft über viele Programmteile verstreut: ein Teil im Code der Bildschirmmaske, ein Teil in Datenbankabfragen, ein Teil in einem nächtlichen Programm. Nach einigen Jahren weiß dann niemand mehr, wo die Regel „Bei Glasschäden entfällt die Selbstbeteiligung“ umgesetzt ist. Soll sich die Regel ändern, übersieht man leicht einen dieser Programmteile.
  • Fachleute stehen regelmäßig für die Zusammenarbeit zur Verfügung. Ohne die Fachleute legt das Entwicklungsteam die Begriffe allein fest, und von Domain Driven Design bleiben nur Bausteine wie Aggregate und Repository, deren Aufrufe die Fachabteilung nicht versteht.

Domain Driven Design ist nicht das Richtige

  • Ein Programm erfasst hauptsächlich Daten und zeigt sie an, hat aber kaum fachliche Regeln. Ein Beispiel ist ein Verzeichnis der Partnerwerkstätten mit Name, Adresse und Telefonnummer. Ein Aggregate Root hätte dann keine Regel zu prüfen, und über Wörter wie „Adresse“ gibt es kaum Missverständnisse. Dann baut das Entwicklungsteam das Programm als Layered Architecture, die das Programm in Schichten für Anzeige, Verarbeitung und Datenzugriff teilt. Die Datenobjekte haben dabei dieselben Felder wie die Tabellen der Datenbank. Die regelmäßigen Arbeitstreffen mit der Fachabteilung und Bausteine wie Aggregate und Repository entfallen.
  • Ein Programm wird nur kurze Zeit gebraucht. Ein Beispiel ist eine einmalige Datenübernahme aus einem Altsystem. Die gemeinsame Sprache verhindert Missverständnisse bei späteren Änderungswünschen. Nach der Datenübernahme wird das Programm aber abgeschaltet und bekommt keine Änderungswünsche mehr. Dann schreibt das Entwicklungsteam das Programm mit den Feldnamen der beteiligten Datenbanken, und die Arbeitstreffen zur gemeinsamen Sprache entfallen.
  • Die Fachleute haben keine Zeit für regelmäßige Arbeitstreffen. Das Entwicklungsteam würde die Begriffe dann allein festlegen, und die Fachabteilung verstünde den Code nicht besser als vorher. Stattdessen klärt das Entwicklungsteam einzelne Änderungswünsche direkt mit der Fachabteilung und baut die Anwendung ohne die Bausteine von Domain Driven Design. Das Entwicklungsteam spart so den Aufwand für Bausteine wie Aggregate und Repository, die ohne gemeinsame Sprache den Code für die Fachabteilung nicht verständlicher machen.
  • Das Entwicklungsteam will nur den Code mit den fachlichen Regeln von Datenbank und Oberfläche trennen. Dafür braucht es keine gemeinsame Sprache mit der Fachabteilung, denn die Trennung betrifft nur, wie der Code auf Datenbank und Oberfläche zugreift. Dann baut das Entwicklungsteam eine Hexagonal Architecture, in der der Code mit den fachlichen Regeln nur über festgelegte Schnittstellen auf Datenbank und Oberfläche zugreift. Die Arbeitstreffen mit der Fachabteilung zur gemeinsamen Sprache entfallen.

Nachteile und typische Fehler

Die gemeinsame Klärung braucht Zeit und die dauerhafte Mitarbeit von Fachleuten, die dafür aus ihrer Tagesarbeit geholt werden. Die gemeinsame Sprache muss gepflegt werden, wenn sich Fachbegriffe ändern. Die Fachbegriffe von Domain Driven Design selbst, etwa Aggregate oder Bounded Context, sind für Einsteiger schwer zugänglich und werden oft missverstanden. Das Entwicklungsteam braucht deshalb Einarbeitung und Erfahrung. Falsch abgegrenzte Bereiche lassen sich später nur mit viel Aufwand korrigieren. Zwischen den Bereichen entsteht zusätzlicher Code, der Begriffe übersetzt, etwa eine Zahlung der Regulierung in einen Betrag der Rückforderung. Domain Driven Design garantiert auch nicht, dass die Regeln fachlich richtig sind. Mit Domain Driven Design steht eine Regel wie die Prüfung der Deckungssumme in einem benannten Baustein, hier im Baustein Schaden, und Fachleute und Entwickler können die Regel dort gemeinsam prüfen.

Typische Fehler und woran man sie erkennt:

  • Nur technische Bausteine: Das Entwicklungsteam übernimmt Entities, Aggregates und Repositories, lässt aber die gemeinsame Sprache aus. Das Entwicklungsteam erhält zusätzliche Bausteine im Code, aber die Fachabteilung versteht den Code nicht besser. Erkennbar ist das an Programmteilen mit Namen aus Domain Driven Design, deren Aufrufe die Fachabteilung nicht versteht.
  • Falsch abgegrenzte Bereiche: Das Entwicklungsteam legt Regulierung und Rückforderung in einen gemeinsamen Bereich. Das Wort „Zahlung“ bedeutet in der Regulierung eine Auszahlung an den Kunden und in der Rückforderung einen Betrag, den der Verursacher an die Versicherung zahlt. Der Baustein Zahlung muss dann beide Bedeutungen abdecken, und jede Änderung für die Rückforderung ändert auch Code, den die Regulierung benutzt. Erkennbar ist das an Änderungswünschen der Rückforderung, für die das Entwicklungsteam regelmäßig auch Code der Regulierung ändern und testen muss.
  • Regel am Aggregate vorbei: Ein nächtliches Programm übernimmt Zahlungen aus einem Altsystem und schreibt sie mit einer eigenen Datenbankabfrage direkt in die Datenbank. Dabei ruft das nächtliche Programm weder das SchadenRepository noch reguliere(betrag) auf. Die Deckungssumme prüft dann niemand. Erkennbar ist das an Schäden, deren Zahlungen zusammen die Deckungssumme übersteigen.
  • Zu großes Aggregate: Das Entwicklungsteam packt den Schaden, alle Dokumente und den ganzen Schriftverkehr in ein Aggregate. Der Regulierungsdienst lädt dann für jede Zahlung über das SchadenRepository den Schaden mit allen Dokumenten und Briefen, bei einem lange laufenden Schaden Hunderte Objekte. Ein Kollege legt gleichzeitig einen Brief zum selben Schaden ab. Beide ändern dasselbe Aggregate, und das SchadenRepository weist einen der beiden Speichervorgänge zurück, obwohl Zahlung und Brief nichts miteinander zu tun haben. Erkennbar ist das an langen Ladezeiten und häufigen Konflikten beim Speichern.
  • Veraltete Sprache: Nach dem Ende der Arbeitstreffen führt die Schadenabteilung einen neuen Begriff ein. Das Entwicklungsteam erfährt davon erst bei einem späteren Änderungswunsch, und im Code steht weiter der alte Begriff. Erkennbar ist das, wenn Gespräche über Änderungswünsche wieder mit Übersetzungen beginnen.

Weiterführend

Verwandte Ansätze

  • Bounded Context: erklärt ausführlicher den abgegrenzten Bereich, den zentralen Begriff des strategischen Designs.
  • Anti Corruption Layer: ein Programmteil, der die Codewerte eines fremden Systems in die Begriffe des eigenen Bereichs übersetzt, damit die Codewerte nicht in den eigenen Code gelangen.
  • Repository: erklärt den Baustein für das Laden und Speichern ausführlicher.
  • Event Sourcing und CQRS: lassen sich mit Domain Driven Design verbinden, sind aber keine Voraussetzung dafür.

Werkzeuge und Umsetzungen

  • Context Mapper: Textsprache und Werkzeuge, um Bereiche und ihre Beziehungen zu beschreiben und als Grafik darzustellen.
  • Egon: Werkzeug für Domain Storytelling, bei dem Fachleute ihre Arbeitsabläufe erzählen und die Abläufe als Bildgeschichte aufgezeichnet werden. So entstehen Begriffe für die gemeinsame Sprache.
  • jMolecules: kennzeichnet Entities, Aggregates und Value Objects im Java-Code, damit Prüfwerkzeuge kontrollieren können, ob der Code die Regeln von Domain Driven Design einhält, etwa dass ein Aggregate nicht direkt auf Objekte in einem anderen Aggregate verweist.
  • Axon Framework: Java-Rahmenwerk mit Bausteinen für Aggregates, Domain Events und CQRS.

Quellen