Domain Driven Design -Teil 4

Strategic Design – Stabile Software durch gute Beziehungen

In den letzten Artikeln wurde festgestellt, wie wichtig ein Domänenmodell und eine domänenbezogene Teamsprache für die Erstellung langlebiger und stabiler Software sind.

Wie schafft man es aber, die Entwicklung so zu organisieren, dass große und komplexe Projekte nicht in kleinteiligen Modellen und einer unübersichtlichen Sprache versanden?

Domain Driven Design liefert darauf eine klare Antwort: Es müssen Zuständigkeiten und Beziehungen definiert werden. Nicht alle Teile eines Systems können oder müssen in einem großen Modell erfasst werden. Wichtiger ist es, Teile, sogenannte Bounded Contexts, zu identifizieren, ihre Grenzen genau abzustecken und ihre Beziehungen zu anderen Modellteilen transparent zu machen.

Ein Bounded Context ist ein Teil des Gesamtsystems, der klar vom Rest des Systems getrennt und von einem Team entwickelt werden kann. Insbesondere hat innerhalb eines Bounded Context jeder Begriff der ubiquitären Sprache genau eine definierte Bedeutung. Zwischen Bounded Contexts gibt es im Allgemeinen keine Wiederverwendung von Code-Teilen, keine geteilten Datenbank-Tabellen usw.

Damit wird in Kauf genommen, dass unter Umständen aus unterschiedlichen Perspektiven derselbe Sachverhalt mehrfach entwickelt wird. Die Entscheidung für die Grenze eines Bounded Context wird also bewusst unter Berücksichtigung möglicher Mehrkosten durch erhöhten Entwicklungsaufwand getroffen und sorgfältig durchdacht. Sind die Kosten zu hoch, macht es vielleicht Sinn, Kontexte zusammen zu entwickeln oder die Beziehung der Kontexte entsprechend zu gestalten.

Allein die Tatsache, dass dem Verhältnis von Bounded Contexts zueinander ein großer Stellenwert beigemessen wird, hilft Probleme zu vermeiden. Zusätzlich schlägt Eric Evans in seinem Buch Tackling Complexity in the Heart of Software verschiedene Patterns vor, mit denen sich die Kommunikation zwischen Bounded Contexts formalisieren lässt.

Wenn zwei Kontexte zwar nicht zusammen entwickelt werden sollen, sich aber auch nicht streng voneinander trennen lassen, sondern Überschneidungen aufweisen, kann das Shared-Kernel-Pattern verwendet werden. Hier wird genau definiert, welche Teile des Codes oder der Infrastruktur gemeinsam genutzt werden. Besonders wichtig sind gemeinsame Tests und eine gemeinsame ubiquitäre Sprache, um fehlerhafte Verwendung des Codes zu vermeiden.

 

Das setzt voraus, dass alle Bounded Contexts, die Anteil am geteilten Code haben, gleichberechtigt darauf zugreifen und Änderungen anstoßen können. Wenn dies z.B. aufgrund der Priorisierung von Projekten nicht gegeben ist, kommt ein Team in die Rolle von einem anderen Team abhängig zu sein. Das führt schnell zu Frustrationen und Verzögerungen, wenn nicht klar definiert wird, welche Rechte und Pflichten beide Teams haben. Wenn es eine zentrale Steuerungseinheit, zum Beispiel einen Abteilungs- oder Projektleiter, gibt, können beide Teams in einer Customer-Supplier-Beziehung entwickeln. Das Supplier-Team entwickelt unabhängig ein eigenes Domänenmodell, muss aber dem Customer-Team die benötigten Schnittstellen oder Informationen zur Verfügung stellen. Das Customer-Team passt sich an die vom Supplier-Team vorgegebene Struktur an, hat aber ein Veto-Recht bei Entscheidungen, die seine Arbeit behindern würden. Wichtig sind hier regelmäßige gemeinsame Integrationstests. So kann sich das Supplier-Team darauf verlassen, dass alle Änderungen vom Customer-Team berücksichtigt werden.

Wenn das Supplier-Team nicht mit dem Customer-Team zusammenarbeiten kann, ist dieses Pattern nicht anwendbar und würde die Entwicklung auf Seiten des Customer-Teams stark beeinträchtigen. Stattdessen empfiehlt Eric Evans das Conformist-Pattern, bei dem ein Team dem Design eines anderen Bounded Context folgt. Strukturentscheidungen werden damit zugunsten einer Kooperation abgegeben. Sinnvoll ist so ein Vorgehen auch bei ausgereiften Legacy-Systemen, die zwar nicht beeinflusst werden können, deren Struktur aber die Domäne gut erfasst.

Bestehende Systeme, die nicht geeignet sind, um Designentscheidungen vorzugeben, können mit einem Anticorruption Layer angebunden werden. Dieser Layer ist eine Art Übersetzungsschicht, die einem Bounded Context ermöglicht, ein eigenes Modell mit einer eigenen ubiquitären Sprache zu entwickeln, aber trotzdem auf einen anderen Kontext zuzugreifen.

Wichtig ist in jedem Fall die Abwägung von Vor- und Nachteilen eines bestimmten Patterns in der konkreten Situation. Wenn eine Verbindung von zwei Bounded Contexts hohe Kosten verursacht, sollte überlegt werden, ob sie wirklich nötig ist, oder ob beide Teams nach dem Pattern Separate Ways arbeiten. Das bedeutet, dass keine Wiederverwendung von Code-Teilen oder Infrastruktur stattfindet, dass jedes Team sein eigenes Modell erstellt und dass die Teile sich nicht gegenseitig beeinflussen.

Wenn ein Team zwar unabhängig von anderen Modellen entwickeln soll, aber mehrere weitere Bounded Contexts auf Funktionalitäten oder Daten zugreifen müssen, besteht die Möglichkeit, Open Host Services anzubieten. Benötigte Services werden so allgemein wie möglich formuliert und den anderen Teams zur Verfügung gestellt.

Wichtiger als die Frage, welches der vorgestellten Patterns im Einzelfall verwendet werden sollte, ist das grundsätzliche Bewusstsein dafür, dass es sinnvoll ist, komplexe Domänen in Bounded Contexts abzubilden und deren Beziehungen sorgfältig zu überdenken. Zwischen den beiden Extremen, dass jedes kleine Projekt unabhängig vom Gesamtsystem entwickelt oder dass Wiederverwendung und vollständige Integrität um jeden Preis durchgesetzt wird, gibt es noch viele weitere Möglichkeiten, die auch in Kombination miteinander eingesetzt werden können.

Das spart nicht nur Zeit bei der Entwicklung und in der Kommunikation, sondern beseitigt auch Frustrationsquellen, wie unklare hierarchische Beziehungen und unterschiedlich verstandene Abhängigkeiten. Die Visualisierung der Bounded Contexts und ihrer Beziehungen in einer Context Map schafft außerdem einen Überblick über das Gesamtsystem und vereinfacht Weiterentwicklung und Wartung.

Strategic Design hilft also nicht nur, Softwareentwicklung besser zu strukturieren und für alle Beteiligten nachvollziehbar zu priorisieren, sondern liefert auch konkrete Lösungsansätze für Implementierung und Projektplanung. Zusammen mit der Verwendung einer ubiquitären Sprache und der Erstellung eines Domänenmodells ist Strategic Design die Basis für eine nachhaltige und stabile Softwareentwicklung – insbesondere für komplexe Systeme.


Jetzt teilen: