Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Lire en français

Abschnitt 5.7 von 5.8 · Mittelstufe · 26 Min. Lesezeit

Strategisches Domain-Driven Design: Bounded Contexts und Context Maps

Videolektion

5 Min. · auf Englisch gesprochen, mit Untertiteln
Nur anhören (Audio)
Transkript lesen

Jeder Stil in diesem Kapitel braucht Grenzen. In dieser Lektion lernen Sie die gängigste Methode kennen, sie im Geschäft selbst zu finden: strategisches Domain-Driven Design.

In einem Krankenhaus bedeutet das Wort Patient für jede Abteilung etwas anderes. Für die Aufnahme sind es ein Name, eine Adresse und ein Termin. Für die Station sind es ein Bett, Medikamente und Beobachtungen. Für die Abrechnungsstelle sind es ein Versicherer und eine Rechnung. Ein riesiges Formular für alle wäre gewaltig, und alle würden darum streiten. Besser funktioniert es, wenn jede Abteilung ihre eigene einfache Akte führt und man sich einigt, wie Informationen weitergegeben werden.

Domain-Driven Design, kurz DDD, wurde 2003 von Eric Evans eingeführt. Sein Argument: Die schwierigsten Probleme in Geschäftssoftware sind meist nicht technisch, sondern liegen im Verständnis der Domäne. Also wird Software um ein Modell der Domäne herum gebaut, gemeinsam mit Fachexperten entwickelt. Alle verwenden ein gemeinsames Vokabular, die Ubiquitous Language, in Gesprächen, Dokumenten, Tests und Code. Wenn zwei Gruppen dasselbe Wort unterschiedlich verwenden, haben Sie einen Hinweis auf zwei verschiedene Modelle gefunden. Dort beginnen Grenzen.

Teilen Sie das Geschäft zuerst in Subdomänen auf, und ordnen Sie sie ein. Die Core Domain ist das, was das Unternehmen unterscheidet. Für ein Essenslieferunternehmen ist das wahrscheinlich die Disposition: Bestellungen Kurieren zuordnen und Ankunftszeiten vorhersagen. Sie verdient Ihre besten Leute und Ihre sorgfältigste Modellierung. Eine Supporting Subdomain ist spezifisch und notwendig, aber keine Quelle von Vorteilen, etwa die Speisekartenverwaltung. Sie verdient ein solides, aber einfacheres Design. Eine generische Subdomäne ist ein Problem, das viele Unternehmen teilen, etwa Zahlungen oder Identität. Meist sollten Sie sie zukaufen oder eine dünne Schicht über einer vorhandenen Lösung bauen.

Nun die Kernidee. Ein Bounded Context ist eine Grenze, innerhalb derer ein Modell und eine Sprache konsistent gelten. Hier sieht eine Kundenbestellung in drei Kontexten unterschiedlich aus. In den Bestellungen ist sie ein Warenkorb mit Preisen, Rabatten und einem Zahlstatus. In der Disposition ist sie ein Lieferauftrag: Abholung, Übergabe, Fertigzeit und ein Kurier. Und in der Küche ist sie ein Ticket: Gerichte, Notizen, Allergene und Zubereitungszeit. Gemeinsam haben sie nur die Bestell-ID und die Events, die zwischen ihnen fließen. Jedes Modell bleibt klein, weil es einem Zweck dient.

Eine Context Map zeigt die Kontexte und ihre Beziehungen. Diese gehört zur Essenslieferplattform, wo eine einzige gemeinsame Order-Klasse auf vierundneunzig Felder angewachsen war. Die Architektin führte einen zweitägigen Event-Storming-Workshop durch, in dem Fachleute aus dem Geschäft und Engineers die Events der Domäne auf eine Zeitleiste setzten, und zeichnete daraus diese Map. Die Disposition ist die Core Domain. Bestellungen und Disposition stehen in einer Partnership: Sie haben gemeinsam Erfolg oder scheitern gemeinsam, also planen und releasen sie die Übergabe zusammen. Die Speisekarte beliefert die Bestellungen als Customer und Supplier: Die Bedürfnisse der Bestellungen kommen ins Backlog der Speisekarte. Zu Systemen, die er nicht kontrolliert, baut ein Kontext einen Anticorruption Layer, der übersetzt, damit das fremde Modell nicht durchsickern kann. Die Speisekarte tut das für die Kassensysteme der Restaurants, die Zahlungen für den Zahlungsanbieter. Die Disposition dagegen ist ein Conformist: Sie nutzt das Modell des Kartenanbieters unverändert. Das ist eine bewusste Wahl, denn eine Übersetzung würde mehr kosten, als sie einspart. Die Zahlungen bieten allen eine offene, dokumentierte API, und Bestell-Events haben ein veröffentlichtes, versioniertes Format.

Bounded Contexts sind der beste Leitfaden für Modul- und Servicegrenzen. Jeder hat sein eigenes Modell, seine Sprache, seine Daten und idealerweise ein verantwortliches Team. Auf der Plattform wird jeder Kontext zuerst ein Modul eines modularen Monolithen, mit eigenem Schema. In jedem Modul hält die hexagonale Struktur aus Abschnitt fünf Punkt sechs das Modell unabhängig von der Infrastruktur. Die Disposition mit ihren ganz anderen Skalierungs- und Release-Anforderungen ist der erste Kandidat für einen späteren eigenen Service. Ihre Grenze hat bereits die richtige Form. Deshalb sagen viele Fachleute: Erst die Kontexte finden, dann über das Deployment entscheiden. Services, die quer zu den Kontexten liegen, ergeben einen eng gekoppelten verteilten Monolithen.

Der vollständige Abschnitt behandelt alle acht Beziehungsmuster, wie Event Storming funktioniert und Bausteine wie Aggregate. Als Nächstes fragen wir in Abschnitt fünf Punkt acht, dem letzten dieses Kapitels, wann ein Monolith die richtige Antwort ist. Bis gleich.

Was Sie wissen müssen

Warum Domain-Driven Design in ein Kapitel über Struktur gehört

Jeder Stil in diesem Kapitel braucht Grenzen. Ein modularer Monolith braucht Modulgrenzen; die Microservices aus Kapitel 6 brauchen Servicegrenzen; selbst eine hexagonale Anwendung muss wissen, was in das Hexagon gehört. Domain-Driven Design (DDD) ist die am weitesten verbreitete Methode, diese Grenzen in der Fachdomäne selbst zu finden statt in technischen Schichten.

Eric Evans führte es in Domain-Driven Design: Tackling Complexity in the Heart of Software (Addison-Wesley, 2003) ein. Sein Argument: In den meisten Geschäftsanwendungen liegen die schwierigsten Probleme nicht in der Technik, sondern im Verständnis der Domäne, also darin, was eine Police, ein Schadensfall, eine Lieferung oder eine Erstattung wirklich ist und welche Regeln dafür gelten. Software sollte daher um ein bewusst gestaltetes Modell der Domäne herum gebaut werden, das Entwicklungsteams und Fachexperten gemeinsam entwickeln, und dieses Modell sollte vor der Vermischung mit anderen Modellen geschützt werden. 2015 veröffentlichte Evans eine kurze DDD Reference, die die Muster zusammenfasst und einige ergänzt, etwa Partnership und Big Ball of Mud als Context-Map-Muster sowie Domain Events als Baustein.

DDD hat zwei Hälften. Strategisches Design teilt eine große Domäne in Subdomänen und Bounded Contexts auf und bildet die Beziehungen zwischen ihnen ab; das ist Architektur und das Hauptthema hier. Taktisches Design liefert Bausteine (Entitäten, Value Objects, Aggregate und andere), um das Modell innerhalb eines Kontexts zu schreiben; das ist Design und wird kurz behandelt.

Ubiquitous Language

Eine Ubiquitous Language ist ein gemeinsames Vokabular, das Entwicklungsteams und Fachexperten zusammen aufbauen und das in Gesprächen, Dokumenten, Tests und Code konsistent verwendet wird. Wenn die Disponenten sagen, ein Kurier sei „gestapelt“, wenn er zwei Bestellungen aus demselben Restaurant fährt, dann heißt es im Code stack, nicht multiOrderAssignment. Erweist sich ein Begriff als mehrdeutig oder sperrig, ändert das Team Sprache und Code gemeinsam.

Das klingt nach einer Frage der Benennung, hat aber architektonische Folgen. Die Übersetzung zwischen den Worten des Geschäfts und den Worten des Codes ist der Ort, an dem Missverständnisse und damit Fehler entstehen. Und sobald zwei Gruppen dasselbe Wort mit unterschiedlicher Bedeutung verwenden, haben Sie einen Hinweis auf zwei verschiedene Modelle gefunden, und genau das ist der Ausgangspunkt für Bounded Contexts.

Subdomänen: Core, Supporting und generisch

Die Fachdomäne als Ganzes gliedert sich in Subdomänen: Tätigkeitsbereiche wie Speisekarten, Bestellungen, Zahlungen und Disposition. Fachleute ordnen sie drei Arten zu und bauen dabei auf Evans’ Unterscheidung zwischen Core Domain und generischen Subdomänen auf; die Dreiteilung mit Supporting Subdomains wurde von späteren Autoren populär gemacht, insbesondere von Vaughn Vernon in Implementing Domain-Driven Design (2013).

Die Core Domain ist das, was das Unternehmen von seinen Wettbewerbern unterscheidet, und der Grund, warum sich die Software zu bauen lohnt. Für ein Essenslieferunternehmen ist das wahrscheinlich die Disposition: Bestellungen Kurieren zuordnen und Ankunftszeiten vorhersagen. Eine Supporting Subdomain ist spezifisch für das Unternehmen und notwendig, aber keine Quelle von Vorteilen: Speisekartenverwaltung, Kurier-Onboarding. Eine generische Subdomäne ist ein Problem, das viele Unternehmen teilen und für das es gute vorhandene Lösungen gibt: Zahlungen, Identität, E-Mail und SMS.

Die Einordnung ist eine Investitionsentscheidung. Die Core Domain verdient die erfahrensten Leute, die sorgfältigste Modellierung und Code, der Ihnen vollständig gehört, denn dort wird bessere Software direkt zum Geschäftsvorteil. Supporting Subdomains verdienen solide, aber einfachere Designs, oft ein schlichtes Modul in Schichten. Generische Subdomänen sollten meist zugekauft, als Service genutzt oder als dünne Adapter über Open-Source-Komponenten gebaut werden: Einen eigenen Identity Provider zu bauen ist Aufwand an einer Stelle, an der Sie nicht gewinnen können. Die Einordnung ändert sich zudem mit der Zeit; eine unterstützende Fähigkeit kann zur Core Domain werden, wenn sich die Geschäftsstrategie ändert.

Bounded Contexts

Ein Bounded Context ist eine explizite Grenze, innerhalb derer ein Modell und eine Ubiquitous Language konsistent gelten. Innerhalb davon hat „Bestellung“ genau eine Bedeutung. Außerhalb kann dasselbe Wort etwas anderes bedeuten, und das ist in Ordnung, denn jedes Modell ist für einen Zweck gestaltet. Evans stellte fest, dass der Versuch, das Modell eines großen Systems zu einem einzigen zu vereinheitlichen, weder machbar noch wirtschaftlich ist; er bringt riesige gemeinsame Klassen hervor, die jedem Zweck schlecht dienen und die jedes Team ändern muss.

Subdomänen und Bounded Contexts sind verschiedene Dinge, auch wenn sie oft zusammenfallen. Eine Subdomäne ist Teil des Problems: Sie existiert im Unternehmen, ob es Software gibt oder nicht. Ein Bounded Context ist Teil der Lösung: eine Grenze, die Sie für ein Modell wählen, und meist auch für Code und Daten, die einem Team gehören. Ideal ist ein Bounded Context pro Subdomäne, doch in Altsystemen erstreckt sich oft ein Kontext über mehrere Subdomänen, und eine große Subdomäne kann mehrere Kontexte brauchen.

Ein Wort, drei Modelle

Das Wort „Bestellung“ bedeutet in drei Bounded Contexts einer Essenslieferplattform Verschiedenes; jeder Kontext hat sein eigenes Modell, verknüpft über eine Bestell-IDBestellkontextBestellung = Warenkorb,Preise, Rabatte, ZahlstatusStatus: Entwurf, erteilt,bezahlt, storn.Dispo-KontextLieferauftrag = Abholung,Übergabe, Fertigzeit, KurierStatus: wartend, zugewiesen,abgeholt, zugestelltKüchenkontextTicket = Gerichte, Notiz,Allergene, ZubereitungStatus: eingegangen, kocht,fertigGemeinsam nur: die Bestell-ID (A-17) und die Events zwischen den KontextenJedes Modell ist einfach, weil es einem Zweck dient
„Bestellung“ bedeutet für die Bestellungen einen Warenkorb mit Preisen, für die Disposition einen Auftrag mit Abholung und Übergabe und im Küchenkontext eine Liste zu kochender Gerichte; jedes Modell bleibt klein, weil es einem Zweck dient.

Context Maps

Eine Context Map zeigt die Bounded Contexts eines Systems und die Beziehungen zwischen ihnen. Die Beziehungen beschreiben sowohl die technische Integration als auch Macht und Kommunikation zwischen den Teams, weshalb es beim Context Mapping ebenso um Organisationen wie um Code geht. Es ist eine direkte Anwendung des Gesetzes von Conway: Die Map zeigt, wo die Kommunikationswege der Organisation und die Integrationswege des Systems übereinstimmen müssen.

Viele Beziehungen sind Upstream/Downstream: Die Entscheidungen des Upstream-Kontexts wirken sich auf den Downstream-Kontext aus, aber nicht umgekehrt. Das Downstream-Team hat dann die Wahl, wie es damit umgeht, und genau diese Wahl benennen die meisten Muster.

Beziehungsmuster der Context Map

MusterWas es bedeutetWer sich wem anpasstBeispiel Essenslieferung
PartnershipZwei Teams haben gemeinsam Erfolg oder scheitern gemeinsam, also planen sie zusammen und entwickeln ihre Schnittstelle gemeinsam weiter.Beide, nach AbspracheBestellungen und Disposition releasen die Übergabe von der Bestellung zur Lieferung gemeinsam.
Shared KernelZwei Kontexte teilen einen kleinen, ausdrücklich vereinbarten Teil des Modells und seines Codes; Änderungen brauchen die Zustimmung beider Teams.Beide, mit VorsichtDisposition und Kurier-Onboarding teilen eine winzige Kurierprofil-Bibliothek (Fahrzeugtyp, Kurier-ID).
Customer-SupplierDas Upstream-Team (Supplier) verpflichtet sich, die Bedürfnisse des Downstream-Teams (Customer) zu erfüllen, die Teil seiner Planung sind.Upstream bedient DownstreamDie Speisekarte liefert Speisekarten und Preise an die Bestellungen, und die Bedürfnisse der Bestellungen kommen in das Backlog der Speisekarte.
ConformistDas Downstream-Team übernimmt das Upstream-Modell unverändert, weil es keinen Einfluss darauf hat und sich eine Übersetzung nicht lohnt.Downstream passt sich anDie Disposition verwendet das Routen- und Standortmodell des Kartenanbieters direkt.
Anticorruption LayerDas Downstream-Team baut eine Übersetzungsschicht, damit das Upstream-Modell nicht in sein eigenes durchsickern kann.Downstream übersetztDie Speisekarte übersetzt Dutzende Kassensystemformate der Restaurants in ihr eigenes Speisekartenmodell; die Zahlungen kapseln die API des Anbieters ab.
Open Host ServiceDer Upstream-Kontext bietet allen Konsumenten ein klar definiertes Protokoll oder API an, statt für jeden eine eigene Integration.Upstream veröffentlicht einmalDie Zahlungen bieten Bestellungen, Erstattungen und Abonnements eine einzige dokumentierte Zahlungs-API an.
Published LanguageEin dokumentiertes, gemeinsames Austauschformat, oft zusammen mit einem Open Host Service verwendet.Beide nutzen die SpracheBestell-Events (OrderPlaced, OrderCancelled) haben ein versioniertes, dokumentiertes Schema.
Separate WaysDie Kontexte sind überhaupt nicht integriert, weil der Nutzen geringer ist als die Kosten.KeinerDie Lohnabrechnungsberichte für Kuriere laufen in einem separaten Finanzwerkzeug, das per monatlichem Export statt über eine Live-Integration befüllt wird.

Zwei dieser Muster verdienen besondere Aufmerksamkeit. Ein Anticorruption Layer (ACL) ist eine Reihe von Adaptern und Übersetzern, die ein Downstream-Kontext baut, damit ein fremdes Modell, oft ein Altsystem oder ein externer Anbieter, nicht in sein eigenes Modell durchsickern kann. Es ist dieselbe Idee wie der getriebene Adapter aus Abschnitt 5.6, angewandt zwischen Kontexten, und die Standardwahl für die Integration mit allem, was Sie nicht kontrollieren. Ein Open Host Service zusammen mit einer Published Language ist der Weg des Upstream-Kontexts, viele Konsumenten gut zu bedienen: ein dokumentiertes, versioniertes Protokoll statt einer eigenen Integration für jeden. Ein Shared Kernel sollte klein bleiben; ein großer macht aus zwei Kontexten wieder einen, über den zwei Teams streiten, und Evans’ Referenz von 2015 ergänzt Big Ball of Mud als ehrliche Bezeichnung für einen Bereich ganz ohne klare Grenzen, der eingedämmt statt erweitert werden sollte.

Event Storming: die Kontexte entdecken

Grenzen werden mit Fachexperten entdeckt, nicht im Alleingang entworfen. Event Storming, von Alberto Brandolini entwickelt und 2013 erstmals in seinem Blog beschrieben, ist das beliebteste Workshopformat dafür. Die Menschen, die das Geschäft kennen, und die Menschen, die die Software bauen, stehen an einer langen Papierwand und schreiben Domain Events (Dinge, die passiert sind, in der Vergangenheitsform: Bestellung aufgegeben, Kurier zugewiesen, Essen abgeholt) auf orange Haftnotizen und ordnen sie auf einer Zeitleiste an.

Danach ergänzt die Gruppe die Commands, die Events auslösen, die beteiligten Personen und externen Systeme, die Policies, die auf Events reagieren („immer wenn eine Bestellung aufgegeben wird, einen Kurier finden“), und vor allem die Hotspots, an denen Uneinigkeit herrscht oder niemand Bescheid weiß. Gruppen von Events, die eine Sprache und einen Zweck teilen, deuten auf Bounded Contexts hin; Stellen, an denen dasselbe Wort unterschiedlich verwendet wird, deuten auf Grenzen hin; Stellen, an denen eine Übergabe stattfindet, deuten auf Integrationspunkte hin. Eine Big-Picture-Session für ein ganzes Unternehmen dauert ein oder zwei Tage und braucht kaum Vorbereitung außer den richtigen Leuten und viel Wandfläche.

Taktische Muster in Kürze

Innerhalb eines Bounded Context bietet DDD Bausteine für das Modell. Eine Entität hat eine Identität, die bestehen bleibt, während sich ihre Attribute ändern: Eine Bestellung ist dieselbe Bestellung, nachdem sich ihr Status geändert hat. Ein Value Object hat keine Identität und ist allein durch seine Werte definiert: Zwei Money(12.50, "GBP")-Objekte sind austauschbar, und Value Objects sind meist unveränderlich. Ein Domain Event hält etwas Bedeutsames fest, das in der Domäne passiert ist.

Ein Aggregat ist eine Gruppe von Entitäten und Value Objects, die für Änderungen als eine Einheit behandelt wird, mit einer Entität als Wurzel. Code von außen darf nur Referenzen auf die Wurzel halten und das Aggregat nur über sie ändern, damit die Wurzel die Invarianten des Aggregats durchsetzen kann (Regeln, die immer gelten müssen, etwa „nach dem Aufgeben einer Bestellung können keine Positionen mehr hinzugefügt werden“). Eine Transaktion sollte ein Aggregat ändern; andere Aggregate werden per ID referenziert und über Domain Events aktualisiert, was Aggregate zur natürlichen Einheit der Konsistenz und später der Verteilung macht. Ein Repository lädt und speichert ganze Aggregate und wirkt für die Domäne wie eine Collection; seine Schnittstelle ist in hexagonaler Sprache ein getriebener Port.

from dataclasses import dataclass, field
from decimal import Decimal

@dataclass(frozen=True)
class Money:                               # value object: equal if values equal
    amount: Decimal
    currency: str
    def __add__(self, other):
        if other.currency != self.currency:
            raise ValueError("currency mismatch")
        return Money(self.amount + other.amount, self.currency)
    def times(self, n: int):
        return Money(self.amount * n, self.currency)

@dataclass
class OrderLine:                           # entity inside the aggregate
    dish_id: str
    qty: int
    price: Money

@dataclass
class Order:                               # aggregate root
    id: str
    restaurant_id: str                     # other aggregates referenced by ID only
    lines: list[OrderLine] = field(default_factory=list)
    status: str = "draft"
    events: list = field(default_factory=list)

    def add(self, dish_id, qty, price):
        if self.status != "draft":
            raise ValueError("order already placed")    # invariant
        self.lines.append(OrderLine(dish_id, qty, price))

    def place(self, minimum: Money):
        total = Money(Decimal(0), minimum.currency)
        for line in self.lines:
            total = total + line.price.times(line.qty)
        if total.amount < minimum.amount:
            raise ValueError("below restaurant minimum")  # invariant
        self.status = "placed"
        self.events.append({"type": "OrderPlaced", "order_id": self.id, "total": str(total.amount)})
Ein Order-Aggregat: ein Value Object (Money), eine Entität (OrderLine), die Aggregatwurzel, die Invarianten durchsetzt, und ein Domain Event, das beim Aufgeben der Bestellung festgehalten wird.

Das Beispiel hält das Aggregat klein, referenziert das Restaurant per ID, statt ein Restaurant-Objekt zu halten, und setzt seine Regeln in Methoden durch, statt sie den Aufrufern zu überlassen. Ein häufiger Fehler ist das Gegenteil: Aggregate, die so groß sind, dass jede Änderung eines Benutzers dieselben Zeilen sperrt, oder Domänenobjekte, die nur Sammlungen von Gettern und Settern sind, während die Regeln in Service-Klassen liegen (Martin Fowler nennt das das anämische Domänenmodell).

Ausgearbeitetes Beispiel

Ausgearbeitetes Beispiel: eine Context Map für die Essenslieferplattform

Die Plattform ist auf etwa 60 Engineers in acht Teams gewachsen, und jede Änderung scheint die gemeinsame Klasse Order zu berühren, die inzwischen 94 Felder hat. Die Architektin führt einen zweitägigen Event-Storming-Workshop mit Produktmanagern, Betriebspersonal, Restaurant-Support und Engineers durch und zeichnet danach die unten gezeigte Context Map.

1. Die Subdomänen findenAus der Event-Zeitleiste: Speisekartenverwaltung, Bestellung und Bezahlvorgang, Zahlungen, Disposition (Kuriere Bestellungen zuordnen und Ankunftszeiten vorhersagen), Kurier-Onboarding, Kundenbenachrichtigungen, Restaurantintegration.
2. Sie einordnenDie Disposition ist die Core Domain: Schnellere, günstigere Lieferungen gewinnen Kunden und Restaurants. Speisekarte, Bestellungen und Kurier-Onboarding sind Supporting: notwendig und spezifisch für dieses Geschäft, aber nicht dort, wo es im Wettbewerb steht. Zahlungen und Benachrichtigungen sind generisch: gelöste Probleme, die man am besten zukauft oder dünn über einem Anbieter aufbaut.
3. Bounded Contexts ziehenJede Subdomäne bekommt ihr eigenes Modell. „Bestellung“ in den Bestellungen (Warenkorb, Preise, Zahlstatus) wird getrennt vom „Lieferauftrag“ in der Disposition (Abholung, Übergabe, Fertigzeit) und vom „Ticket“ in der Küchenansicht des Restaurants. Aus der Klasse mit 94 Feldern werden drei fokussierte Modelle, verknüpft über die Bestell-ID.
4. Die Beziehungen benennenBestellungen und Disposition: Partnership. Speisekarte zu Bestellungen: Customer-Supplier. Zahlungen: Open Host Service mit einer Published Language und ein Anticorruption Layer zum externen Anbieter. Speisekarte: Anticorruption Layer zu den Kassensystemen der Restaurants. Disposition: Conformist zur Karten-API, eine bewusste Wahl, weil die Übersetzung ihres reichhaltigen Modells mehr kosten würde, als sie einspart.
5. Über die Investition entscheidenDie besten Engineers und die sorgfältigste Modellierung gehen an die Disposition. Die Zahlungen nutzen einen Anbieter über einen dünnen Adapter. Die Benachrichtigungen nutzen einen zugekauften Messaging-Service.
6. Über das Deployment entscheidenDie Kontexte werden zunächst Module der bestehenden Anwendung und machen sie so zu einem modularen Monolithen (Abschnitt 5.3), jedes mit eigenem Schema und eigener öffentlicher Schnittstelle. Die Disposition mit ihren ganz anderen Skalierungs- und Release-Anforderungen ist der erste Kandidat, später ein eigener Service zu werden.

Dass die Bestellungen als Supporting eingeordnet wurden, überraschte einige Stakeholder. Der Maßstab ist nicht die Wichtigkeit (ohne Bestellungen kann das Unternehmen nicht arbeiten), sondern die Differenzierung: Würde ein besseres Bestellmodell dem Unternehmen mehr Kunden bringen als ein besseres Dispositionsmodell?

Die Conformist-Beziehung zum Kartenanbieter ist eine bewusste Abwägung, festgehalten in einem Entscheidungsprotokoll, kein Zufall. Ändert sich der Anbieter oder wird er ersetzt, wird die Disposition das zu spüren bekommen.

Die Context Map

Context Map der Essenslieferplattform: Bounded Contexts, externe Systeme und das Beziehungsmuster an jeder VerbindungRestaurant-Kasseextern, viele AnbieterKarten-APIexternSpeisekarteSupportingBestellungSupportingDispoCoreZahlungengenerischBenachricht.generischKurier-OnboardingSupportingZahlungsanbieterexternUDACL in KarteUDConformistUDCustomer /SupplierPartnershipDUOHS / PLBestell-Events(PublishedLanguage)Shared KernelDUACL in ZahlungU = Upstream, D = DownstreamKorallfarbener Kasten: Core; grauer Kasten: externes SystemGestrichelter Pfeil: async. Events
Jede Linie benennt das Beziehungsmuster; U und D markieren Upstream und Downstream, und die Anticorruption Layers liegen auf der Downstream-Seite zu Systemen, die die Plattform nicht kontrolliert.

Bounded Contexts als Modul- und Servicegrenzen

Bounded Contexts sind der beste verfügbare Leitfaden dafür, wo Modulgrenzen in einem Monolithen und Servicegrenzen in einem verteilten System zu ziehen sind. Ein Kontext hat sein eigenes Modell, seine eigene Sprache, seine eigenen Daten und idealerweise ein verantwortliches Team, und genau diese Eigenschaften machen ein Modul kohäsiv und lose gekoppelt. In einem modularen Monolithen wird jeder Kontext zu einem Modul mit eigenem Schema; darin hält die hexagonale Struktur aus Abschnitt 5.6 das Modell unabhängig von der Infrastruktur.

Muss ein Kontext später separat deployt werden, hat seine Grenze bereits die richtige Form für einen Service, und seine Beziehungen auf der Context Map sagen Ihnen, wie die Integration aussehen muss. Deshalb empfehlen viele Fachleute, zuerst die Kontexte zu finden und erst danach über das Deployment zu entscheiden. Auch das Umgekehrte gilt: Services, die quer zu Bounded Contexts geschnitten sind (ein „Order-Service“, in den jeder Kontext schreibt), ergeben den eng gekoppelten verteilten Monolithen, der in Abschnitt 6.3 behandelt wird. Beachten Sie auch, dass ein Bounded Context nicht automatisch ein Microservice ist; ein Kontext kann als mehrere Services deployt werden, und mehrere kleine Kontexte können sich ein Deployment teilen.

Was strategisches DDD bringt und kostet

AspektWirkungBegründung
Änderbarkeit innerhalb eines KontextsHochEin Modell, eine Sprache und ein Verantwortlicher: Änderungen bleiben lokal und sind leicht zu durchdenken.
TeamautonomieHochKontextgrenzen decken sich mit Teamgrenzen, sodass Teams ihre Modelle ändern können, ohne jede Änderung auszuhandeln.
Passung zwischen Software und GeschäftHochUbiquitous Language und gemeinsame Modellierung verringern Übersetzungsfehler zwischen Fachexperten und Code.
Anfänglicher und laufender AufwandMittel bis hochModellierungssessions, die Zeit der Fachexperten, Übersetzungsschichten und die Pflege von Verträgen kosten alle Aufwand.
Datenduplizierung und IntegrationMittlere KostenJeder Kontext hält seine eigene Kopie dessen, was er braucht, und diese muss über Events oder APIs synchron gehalten werden.
Nutzen bei einfachen DomänenNiedrigBei CRUD-lastigen Systemen mit wenig Logik bringt vor allem taktisches DDD viel Zeremonie mit wenig Ertrag.

Prüfen Sie Ihr Verständnis

Was ist der Unterschied zwischen einer Subdomäne und einem Bounded Context?

Eine Subdomäne ist Teil des fachlichen Problems und existiert, ob es Software gibt oder nicht; ein Bounded Context ist eine in der Lösung gewählte Grenze, innerhalb derer ein Modell gilt. Idealerweise decken sie sich, aber das muss nicht so sein.

Ihr Team integriert ein Abrechnungs-Altsystem, dessen Modell verwirrend ist und das Sie nicht ändern können. Welches Context-Map-Muster schützt Ihr Modell?

Ein Anticorruption Layer: Übersetzer auf Ihrer Seite, die das Modell des Altsystems in Ihre eigenen Begriffe umwandeln, damit es nicht in Ihren Kontext durchsickern kann.

Warum sollte eine Transaktion in der Regel nur ein Aggregat ändern?

Weil das Aggregat die Einheit ist, deren Invarianten zusammen gelten müssen; mehrere auf einmal zu ändern erzeugt große Sperren und Kopplung, während andere Aggregate über Domain Events aktualisiert werden können, was es auch erlaubt, sie später zu verteilen.

Warum sind Zahlungen für ein Essenslieferunternehmen meist eine generische Subdomäne?

Weil das Annehmen von Zahlungen ein gelöstes Problem ist, das viele Unternehmen teilen, und nicht dort liegt, wo das Unternehmen im Wettbewerb steht; es wird daher besser zugekauft oder dünn über einem Anbieter gebaut als tiefgehend modelliert.

Weiter · 5.8Einen Monolithen wählenMonolithFirst, die Microservice Premium und der Majestic Monolith; wann eine einzige Deployment-Einheit die richtige Wahl ist, wie weit ein Monolith skaliert, die Anzeichen, dass es Zeit für eine Aufteilung ist, und eine Entscheidungshilfe für die Stile aus Kapitel 5.Weiter →

Quellen und weiterführende Literatur

Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.

Fragen: „Unsere „Customer“-Tabelle wird von Vertrieb, Support und Abrechnung genutzt, und niemand wagt es, sie zu ändern. Wie würde ich sie mit Bounded Contexts entwirren, ohne alles auf einen Schlag neu zu schreiben?“