© Elya Vatel/Shutterstock.com
Architektur innerhalb eines Bounded Context

DDD: taktisches Design


Im Artikel von Carola Lilienthal und Michael Plöd [1] wurde gezeigt, wie man eine Domäne in mehrere Bounded Contexts aufteilt. Dabei erhalten wir statt einem großen, schwer verständlichen und schwer wartbaren Domänenmodell nun mehrere, besser handhabbare Domänenmodelle. In diesem Teil der Serie schauen wir darauf, wie man das einzelne Domänenmodell konkret implementieren kann.

Bei der Implementierung des einzelnen Domänenmodells ist zweierlei relevant: erstens die gröbere Architektur in Schichten, Hexagonen oder Ringen und zweitens die feinere Architektur, die sich in der Mustersprache in den sogenannten Building Blocks ausdrückt.

Schichten, Hexagone, Ringe

Ursprünglich wurde Domain-driven Design [2], [3], [4] mit einer Schichtenarchitektur (Layered Architecture) als Metapher entworfen [5]. Es gibt vier Schichten (Layers): UI, Application, Domain und Infrastructure Layer. Als einzige Regel in diesem Architekturstil gilt: Oben darf unten benutzen, aber unten nicht oben. In der Softwaretechnik wird dann noch zwischen strikter und nichtstrikter Schichtenarchitektur unterschieden: Bei ersterer darf eine Schicht nur die direkt darunterliegende Schicht verwenden, bei zweiterer alle darunterliegenden Schichten.

Für den Domain Layer, der uns wenig überraschend in Domain-driven Design am meisten interessiert, gibt es verschiedene Arten, ihn zu implementieren: Transaction Script, Table Module, Anemic Domain Model, Domain Model [6]. Ein großer Vorteil von strategischem Design ist, dass wir für unterschiedliche Bounded Contexts unterschiedliche Arten wählen können. Jeder Bounded Context kann also eine andere Architektur implementieren. Taktisches Design kommt dann zum Einsatz, wenn wir die Implementationsart Domain Model verwenden. Dabei modellieren wir die Domäne in Code ganz unabhängig von einer möglicherweise darunterliegenden Datenbank und sonstiger Technologie.

Die Schichtenarchitektur ist eine gute erste Näherung, um zu beschreiben, was erlaubt und was verboten ist. Allerdings reicht sie noch nicht aus. Denn eben haben wir gelernt, dass wir das Domänenmodell von Technologie freihalten sollen. Und da kommt es zu einem Widerspruch, denn die Schichtenarchitektur erlaubt den Zugriff von Domain auf Infrastructure. Andere Autoren haben deshalb Hexagone (Ports and Adapters Architecture) oder Ringe (Clean Architecture, Onion Architecture [7]) als Architekturmetapher vorgeschlagen. Statt oben/unten sprechen wir dann von innen/außen. Statt einem Domain Layer haben wir dann einen Domänenkern, der ganz innen steckt. Je weiter innen ein Architekturelement steckt, desto sauberer soll sein Code sein. Sauber bedeutet hier freier von Technologie und anderen Implementationsdetails.

Eine andere Möglichkeit, darüber nachzudenken, ist, zwischen Domain und Infrastructure Layer das Dependency Inversion Principle (DIP) anzuwenden. Genau das tun wir mit den Repositories, die wir uns später bei den Building Blocks genauer anschauen werden.

LeasingNinja

Unter www.leasingninja.io findet sich der LeasingNinja. Dieses Projekt soll als Lehrbeispiel für DDD dienen. Anhand der Beispieldomäne „Leasing“ wird hier gezeigt, wie man ganz konkret von der Domäne zu Kontextschnitt und Domänenmodell kommt. Der Code, der dieses Modell dann implementiert, ist auf GitHub verfügbar. Mit dem LeasingNinja wird das Vertriebs- und Risikomanagement einer Leasinggesellschaft unterstützt. Ich verwende das Beispiel hier, um zu zeigen, wie taktisches Design im richtigen Leben aussehen kann.

Implementieren von Schichten, Hexagonen und Ringen in Java

Wie drückt man diese Architektur nun in Java aus? Bis einschließlich Version 8 gab es in Java nur eine Möglichkeit, Architektur oberhalb der Klassenebene auszudrücken – das Package. In der Folge wurden und werden Schichten gerne als Packages ausgedrückt. Als Beispiel hier die Architektur des LeasingNinja (Kasten: „LeasingNinja“) [8] (Abb. 1):

schwentner_ddd_4_1.tif_fmt1.jpgAbb. 1: Architektur des LeasingNinja
  • package io.leasingninja.sales.userinterface

  • package io.leasingninja.sales.application

  • package io.leasingninja.sales.domain

  • package io.leasingninja.sales.infrastructure

Gut zu erkennen ist die typische Namenskonvention produkt.boundedcontext.layer. Im Beispiel sehen wir also die Schichten des Bounded Context „Sales“. Der Package-Mechanismus von Java ist nicht perfekt. Immerhin haben wir nun eine logische Gruppierung von Klassen vorgenommen, was ein guter Start ist. Die Architekturregeln, also welche Schicht auf welche anderen Schichten zugreifen darf, können wir zwar mit Packages nicht ausdrücken, doch für viele Projekte reicht das trotzdem aus. Möchte man diese Regeln in der Architektur ausdrücken, kann man OSGI oder – seit Java 9 – die Jigsaw-Module verwenden.

Implementieren von Bounded Contexts

Wie wir Schichten implementieren, hängt auch davon ab, wie wir die Bounded Contexts implementieren. Ein Bounded Context kann z. B. ein Package oder ein Modul oder ein Microservice sein. Ist der Bounded Context ein Package, bekommen wir mit den Schichten ein Problem, da Packages in Java nicht geschachtelt sein können. Das führt dazu, dass entweder die Schichtung nicht als Package ausgedrückt wird oder die Schnittstelle des Bounded Context nicht explizit ist und auch von außerhalb des Bounded Context direkt auf die Elemente innerhalb davon zugegriffen werden kann. Klassen können ja nur entweder public oder package private sein. Wenn die Elemente einer Schicht als public definiert werden, damit sie von der darüberliegenden Schicht verwendet werden können, sind sie gleich für die ganze Welt zug...

Neugierig geworden?

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang