design

Warum Domain Driven Design (DDD)?

Ein großes Problem in der Software Entwicklung ist die Kommunikation. Durch die hohe Spezialisierung, die aufgrund der vielfältigen Aufgabenstellungen notwendig ist, sprechen Entwickler, Projektmanager, Grafiker, Tester und je nachdem, wer noch in einem Projekt involviert ist, unterschiedliche Sprachen. Das kann zu Verständigungsproblemen führen und manchmal sogar zu Fehlern. Die Konsequenz sind verlängerte Entwicklungszeiten und Motivationsprobleme im Team.

Die Alternative zu gutem Design ist schlechtes Design, nicht überhaupt kein Design. (Douglas Martin)

Die meisten Menschen machen den Fehler zu denken, dass es bei Design nur darum geht, wie es aussieht. ... Es geht nicht nur darum, wie etwas aussieht und sich anfühlt. Design ist, wie etwas funktioniert. (Steve Jobs)

Artikel lesen

1. Strategisches Design

Domain: Im Rahmen von DDD versteht man unter der Domäne die Anwendungsdomäne, die fachliche Aufgabenstellung unter der die Software entwickelt wird. Steht die Anwendungsdomäne im Mittelpunkt und ist Basis für die Architektur der Software, dann spricht man von DDD.

Im DDD geht es in erster Linie darum, eine Ubiquitous Language in einem bestimmten bounded context zu modellieren.

Bounded Context: Innerhalb eines bounded context hat jede Komponente eines Softwaremodells eine bestimmte Bedeutung und tut bestimmte Dinge. Die Komponenten innerhalb eines bounded context sind kontextspezifisch und haben ihre Bedeutung und Funktion im Einklang mit den anderen Komponenten im bounded context.

Ubiquitous Language: Innerhalb eines bounded context gilt eine ubiquitous language. Die Begriffe innerhalb dieser Sprache sind eindeutig. Sie wird von den Teammitgliedern genutzt und ist Basis des Softwaremodells. Es muss gewährleistet sein, dass die Sprache jedem Teammitglied zur Verfügung steht.

Bei der Entwicklung der ubiquitous language darf man sich nicht verzetteln. Letztlich besteht das Domänenmodell nicht aus Definitionen, Diagrammen oder Spezifikationen. Das Domänenmodell ist der Code und der Code ist das Modell (Reverse Engineering).

Jeweils einem Team wird ein bounded context zugewiesen. Für jeden bounded context sollte es ein von den anderen getrenntes Quellcode Repository geben. Für jeden bounded context sollte es ein Datenbank-Schema geben, das klar von den anderen abgegrenzt ist. Die Kunst besteht darin, diese Grenzen zu wahren und zu bestimmen. Die Gefahr besteht darin, dass der bounded context zu viel umfasst.

Was sind Hinweise darauf, dass ein bounded context aus dem Ruder läuft?

  • Das Team wird zu groß. Die optimale Teamgröße ist erreicht, wenn das ganze Team von zwei großen Pizzas satt wird.
  • Die Begriffe verschwimmen und bekommen mehrere Bedeutungen.
  • Immer wieder neue ergänzende Konzepte rauben Ressourcen für die Pflege und Tests der bestehenden Komponenten.
  • Die neuen Konzepte gehören nicht mehr zum Kern des bounded contexts.
  • Man scheut die Abgrenzung zu einem neuen bounded context.
  • Man möchte das Risiko eines neuen bounded contexts nicht offenlegen. Dabei ist das Risiko, wenn man nicht abgrenzt, viel größer.

Ein bounded context enthält aber nicht nur das Domänenmodell, sondern auch Komponenten, die sich um die Kommunikation mit der Außenwelt kümmern (Adaptor).

Subdomain: Meistens lässt sich die Geschäftsdomäne nicht mit einem bounded context darstellen. Er wäre zu groß. Daher unterteilt man in subdomains, die für sich wieder einen bounded context definieren.

In Altsystemen ist meist DDD nicht (voll) umgesetzt. Oft entsprechen sie dem Pattern big ball of mud. Da in ihnen keine bounded context festgelegt wurden, spricht man auch von unbounded legacy systems. Man könnte diese Altsysteme in mehrere logische Domänenmodelle unterteilen und für jedes einzelne eine ubiquitous language definieren. Tatsächlich hilft dieses Vorgehen, wenn man auf DDD umsteigt oder das Altsystem in eine Umgebung von anderen DDD-Systemen integrieren will.

Context Mapping: An der Schnittstelle zwischen zwei Subdomains treffen zwei ubiquitous languages aufeinander. Die unterschiedlichen Begriffe und Operationen in den jeweiligen domains müssen auf einander gemappt werden. Dies bezeichnet man als context mapping.

Arten von Mapping

  • Partnership; die Ziele der beiden involvierten Teams sind voneinander abhängig; für die Synchronisierung nutzen sie Continous Integration;
  • Shared Kernel; zwei Teams teilen sich ein kleines gemeinsames Modell als Schnittmenge von zwei bounded context; ein shared kernal erfordert ein hohes Maß an Kommunikation;
  • Customer Supplier; der supplier (upstream) stellt zur Verfügung, was der customer (downstream) braucht;
  • Conformist; der upstream stellt etwas zur Verfügung, was der downstream benutzen darf;
  • Anticorruption Layer (ACL); das downstream Team erzeugt eine Übersetzungsschicht (ACL);
  • Open Host Service; das upstream Team stellt einen offen angebotenen Dienst zur Verfügung; die bereit gestellte API sollte gut dokumentiert sein; Open Host Services sind leichter zu konsumieren als Supplier;
  • Published Language; oft bietet ein Open Host Service eine Published Language an;
  • Separate Ways; keine Verbindung zwischen zwei bounded context;
  • Big Ball of Mud; Kein Kontext Mapping;
    • ungewollte und unzulässige Beziehungen und Abhängigkeiten
    • Eine Änderung schlägt Wellen über das ganze System.
    • Nur über Generationen weitergegebenes Geheimwissen und einzelne Heldentaten --- alle Sprachen auf einmal sprechen --- retten das System vor dem endgültigen Kollaps.

Wenn ein bounded context mit einem big ball of mud System ein context mapping eingeht, so sollte das neue bounded context System unbedingt eine ACL vorschalten, so dass es nicht die ubiquitous language des big ball of mud übernimmt.

Technische Lösungen für Context Mapping

  • Remote Procedure Call (RPC) mit Simple Object Access Protocol (SOAP)
  • RESTful HTTP
  • Messaging
    • ein client-bounded-context abonniert die domain events, die von ihrem eigenen oder einem anderen bounded context versendet werden;
    • at least once delivery: durch eine Empfangsbestätigung durch das Messaging System ist gewährleistet, dass eine Nachricht ausgeliefert wurde;
    • idempotent reciever: der Empfänger kann damit umgehen, dass eine Nachricht mehrmals geschickt wurde, die Operation aber nur einmal ausgeführt werden soll;
    • die versendeten Daten können angereichert oder nachgeladen werden;

2. Taktisches Design

Beim taktischen Design werden Konzepte, die für die Architektur innerhalb eines bounded context angewendet werden können, entworfen.

aggregates:

  • aggregates nehmen Kommandos entgegen oder reagieren auf Ereignisse.
  • Sie überprüfen, ob diese zulässig sind.
  • Anschließend werden sie verarbeitet. Dabei können neue Ereignisse und/oder Kommandos ausgelöst werden.
  • Das aggregate darf Daten zu dem Geschäftsvorgang speichern.
  • Das aggregate muss vor und nach der Verarbeitung konsistent sein.
  • Das bezieht sich aber nur auf das aggregate selbst und schließt gleichzeitig nicht andere aggregates mit ein.
  • Der Zustand vor und nach der Verarbeitung muss reproduzierbar sein(unittest).
  • aggregates sind Zusammenfassungen von entities und value objects und deren Assoziationen untereinander zu einer gemeinsamen transaktionalen Einheit.
  • Von außen kann nur mit genau einer entity auf das aggregate zugegriffen werden.
  • Auf die inneren entities darf nicht direkt zugegriffen werden.

Entities, reference objects: Objekte, welche nicht durch ihre Eigenschaften, sondern durch ihre Identität definiert werden. Beispiel: Person. Die Objekte werden mit eindeutigen Identifikatoren modelliert.

value objects: Objekte, die durch ihre Eigenschaft definiert werden; immutable objects, wiederverwendbar,

Regeln für aggregates

  • Schütze die fachliche Konsistenz von aggregates. Nach einem commit einer transaction müssen die Daten konsistent sein.
  • Entwirf kleine aggregates, dann ist die Einhaltung der ersten Regel leichter. Single Responsibility Principle (SRP).
  • Referenziere andere aggregates nur über ihre Identität.
  • Aktualisiere andere aggregates unter Verwendung von eventual consistency.
  • Wähle das Abstraktions-Level mit Bedacht. Ein zu hohes Abstraktions-Level führt zu vielen Spezialfällen, zu zu viel code. Man wird nie alle zukünftigen Bedürfnisse abdecken können.
  • Sichere aggregates durch unittests ab.
  • Ausgangsbasis für die Modellierung von aggregates kann ein Aktionsdiagramm sein, in welchem die Ereignisse in der Domäne zusammengefasst werden.

3. Fazit

Im Kern von DDD geht es darum,

  • geschäftliche Zusammenhänge exakt zu analysieren,
  • eine passende Sprache zu entwickeln und mit ihrer Hilfe die Fachlichkeit in Programmcode abzubilden.

Quellenangaben:

  • ''Domain Driven Design'' von Vaughn Vernon, aus dem Englischen übersetzt von Carola Lilienthal und Henning Schwertner
  • ''Book Design: A Practical Introduction'' von Douglas Martin, 1990
  • Wikipedia: Domain Driven Design https://de.wikipedia.org/wiki/Domain-driven_Design
  • Heise https://www.heise.de/developer/artikel/Domain-driven-Design-erklaert-3130720.html