Software-Architektur: Domain-Driven Design – ein Einstieg

Das Domain-Driven Design (DDD) wurde von E. Evans in seinem Buch Domain-Driven Design: Tackling Complexity in the Heart of Software eingeführt. Für mich hat die Beschäftigung mit dem DDD-Ansatz vor allen Dingen eine (Rück-) Besinnung auf die Konzepte der objektorientierten Programmierung zur Folge gehabt. Diese Aussage mag überraschend klingen, wenn man sich doch über viele Jahre mit der Programmiersprache Java und der Java EE Plattform beschäftigt hat. Wo liegt also das Problem, zumal Java doch eine objektorientierte Programmiersprache ist?

Zunächst rückt DDD mit drei Begriffen die eigentliche Fachlichkeit von Software in das Zentrum der Betrachtung.

Da ist zu aller erst die Domäne selbst. Dies ist der Ausschnitt der realen Welt, der Geschäftsbereich, das Aufgabenfeld usw. wofür ein Software-System realisiert wird. Als Beispiel mag ein Flight Information System (FIS) dienen. Hierin gibt es Aircrafts die viele Flights auf bestimmten Routes ausführen. Auf der anderen Seite gibt es Customer, die eine Booking für einen bestimmten Flight durchführen. Eine von einem Customer getätigte Booking kann aus mehreren BookingPositions je Passenger bestehen und wird durch eine Invoice abgerechnet. Offensichtlich liegt also einer Domäne in der Regel ein Datenmodell zugrunde. Darüber hinaus umfasst eine Domäne aber auch Verhalten, Prozessabläufe, Geschäftsregeln oder Ereignisse.

Um eine solche Domäne für die Software-Entwicklung greifbar zu machen, wird sie durch ein Domänen-Modell in einer abstrahierten Form beschrieben. Zur Beschreibung von Domänen-Modellen sind grafische Software-Spezifikationssprachen wie insbesondere die Unified Modeling Language (UML) oder spezielle textuelle Software-Spezifikationssprachen in Form einer Domain Specific Language (DSL) geeignet. Das Domänen-Modell spezifiziert die Gesamtheit von Daten, Verhalten, Prozessen, Regeln, Ereignissen usw., welche die Domäne ausmachen. Somit beinhaltet ein UML-Domänen-Modell nicht nur Klassen-Diagramme für das Datenmodell, sondern beispielsweise auch ein Zustands-Diagramm für den BookingState oder ein Aktivitäts-Diagramm für den Geschäftsprozess cancel flight.

Die implementierte Anwendung ist eine exakte Repräsentation des Domänen-Modells in Form von Source-Code. Damit dies gelingt, müssen für die Erstellung des Domänen-Modells und dessen Implementierung auf der einen Seite die Software-Entwickler und auf der anderen Seite die fachlichen Experten sehr eng und permanent zusammen arbeiten. Im Zuge dieser Zusammenarbeit entsteht nach und nach die Ubiquitous Language für die Domäne. Dies ist die allgegenwärtige fachliche Sprache, welche alle für die betrachtete Domäne relevanten Begriffe eindeutig definiert. Die Ubiquitous Language für das Flight Information System beinhaltet somit Begriffe wie order, departure time, reserve seat, offered price usw. Für ein Projekt-Team von Software-Entwicklern und fachlichen Experten gilt, dass die Ubiquitous Language „von jedem gesprochen“ und „von jedem eindeutig verstanden“ wird.

Das Resultat einer erfolgreichen Umsetzung des DDD-Ansatzes ist ein Software-System, welches das spezifizierte Domänen-Modell mit den Begriffen der Ubiquitous Language implementiert. Sowohl Modell als auch Implementierung der Domäne nutzen ausschließlich das Vokabular der Ubiquitous Language, deren Begriffe somit in UML-Diagrammen, DSL-Spezifikatinen, Test-Beschreibungen, Source-Code, Wiki-Dokumentation usw. einheitlich verwendet werden.

Wie sieht nun aber die konkrete Vorgehensweise des DDD für die Implementierung aus und warum spielt hierbei die Objektorientierung eine so wichtige Rolle?

Zur Klärung dieser Frage begebe ich mich auf eine Zeitreise zurück auf die Anfänge meines Programmierer-Daseins. Nach einem kurzen Intermezzo mit BASIC war Pascal die erste Programmiersprache, mit der ich mich ernsthaft auseinender gesetzt habe. Ein kleines Pascal-Beispiel deuten die folgenden Code-Ausschnitte an, nämlich die typische Vorgehensweise bei der prozeduralen Programmierung mit der Definition von Datenstrukturen und der Implementierung von Algorithmen darauf:


PROGRAM myapp;

TYPE
person = RECORD
name: STRING;
home: address;
END;
address = RECORD
...
END;

PROCEDURE foo;
VAR p: person;
a: address;
BEGIN
...
END {foo};

BEGIN
...
END {myapp}.

Nach vielen Jahren modularer Programmierung mit Modula-2 folgte dann der Übergang zur Objektorientierung mit Smalltalk. Ich lernte Sätze wie „alles ist ein Objekt“ und „ein Objekt kapselt Daten und das darauf definierte Verhalten ein“.

Mit dem Einsatz von Java und der Java EE Plattform im aktuellen Jahrtausend gelangen wir nun wieder in die Gegenwart. Wie dort ein leichtgewichtiges Java EE Programm, welches mit Maven gebaut wird, oftmals aussieht deuten die folgenden Code-Ausschnitte an:


<project ...>

<artifactId>myapp</artifactId>
<packaging>war</packaging>

@Entity
public class Person {

private String name;
private Address home;
----
@Embeddable
public class Address {
...

@RequestScoped
public class PersonService {

public void foo(Person p) {
Address a = p.getHome();

Vergleicht man nun die Beispiele in Pascal und Java EE, dann kann man sich fragen, was aus dem objektorientierten Konzept der Einkapselung von Daten und Verhalten geworden ist: Die anämische (blutleere) JPA-Entity-Klasse Person kapselt nur die Datenstruktur für Personen ein, d. h. außer Dingen wie einem Konstruktor, den standardmäßigen Getter-/Setter-Methoden oder trivialen Methoden wie z. B. toString() findet man dort kein fachliches Verhalten vor. Dieses wird stattdessen in einer separaten CDI-Service-Klasse PersonService implementiert, deren Methoden (= Algorithmen) auf den Personen-Objekten (= Datenstruktur) operieren.

Diese Art der Programmierung fühlt sich für mich inzwischen problematisch an.

Das DDD definiert nun eine Reihe von Building Blocks als die konstituierenden Elemente eines Software-Systems. Im Sinne der Objektorientierung kann man sich jeden Building Block als ein Entwurfsmuster für eine bestimmte Art von Klassen mit einer festgelegten Aufgabenstellung vorstellen. Innerhalb der Menge der Building Blocks spielt dabei das Entity eine zentrale Rolle.

Ein Entity repräsentiert ein Geschäftsobjekt im klassischen Sinn der Objektorientierung: Das Entity kapselt die benötigten Geschäftsdaten zusammen mit den darauf operierenden Methoden ein. Jedes Entity besitzt eine eindeutige und unveränderliche Objekt-Identifikation. Schließlich sind Entities im allgemeinen Verständnis persistente Objekte.

Die letztgenannte Eigenschaft legt die Implementierung von Entities im Sinne des DDD als JPA-Entity-Klassen nahe. Um jedoch auch reichhaltige Funktionalität in Form von Methoden in einer JPA-Entity-Klasse realisieren zu können, fehlt solchen Klassen die Möglichkeit, sich per CDI beliebige andere „Dienste“ in Form von entsprechenden Objekten injizieren zu lassen. Diese Injektions-Fähigkeit lässt sich jedoch durch die folgende Utility-Klasse leicht nachrüsten, wobei nur die Möglichkeiten von Standard Java EE (CDI 1.1) ausgenutzt werden (vereinfachte Darstellung):


public abstract class Injector {

public static void injectFields(Object entity) {

BeanManager beanManager = CDI.current().getBeanManager();
AnnotatedType annotatedType = beanManager.createAnnotatedType(entity.getClass());
InjectionTarget injectionTarget = beanManager.createInjectionTarget(annotatedType);
CreationalContext creationalContext = beanManager.createCreationalContext(null);
injectionTarget.inject(entity, creationalContext);

Das folgende Beispiel deutet eine Anwendung an, indem in der Entity Booking des Flight Information System ein „Dienst“ vom Typ BookingPositionRepository injiziert wird. Im Konstruktor der JPA-Entity-Klasse wird diese Injektion aufgelöst, so dass etwa innerhalb einer Methode der Klasse Booking eine zugeordnete BookingPosition persistiert werden kann:

@Entity
public class Booking {

@Inject
@Transient
BookingPositionRepository repositoryBookingPosition;

protected Booking() {
Injector.injectFields(this);
}

public void addPassenger(...) {
...
BookingPosition bookingPosition = this.factoryBookingPosition.create(...);
...
this.repositoryBookingPosition.persist(bookingPosition);
...

Durch diese Erweiterung erhält man die so genannten Rich Entities mit den folgenden Eigenschaften:

  • Einkapselung der persistenten Daten eines Geschäftsobjektes
  • Implementierung aller Methoden, die ausschließlich auf den eingekapselten Daten operieren
  • Implementierung von reichhaltigen Methoden vermöge von injizierten, beliebigen „Diensten“

Da nun vielfältige Geschäftslogik direkt in den Entities implementiert werden kann, erübrigt sich die Notwendigkeit, dass solche Funktionalität in mehr oder weniger willkürlich gewählten Service-Klassen implementiert werden muss, weil nur dort Injektionen mit @Inject möglich sind. Somit wird die Geschäftslogik ganz überwiegend direkt an den Geschäftsobjekten implementiert, wodurch die Geschäftslogik im gesamten Software-System einfach und schnell lokalisierbar bleibt.

Soweit ein erster Überblick über wesentliche Konzepte des Domain-Driven Design und die Realisierung von Rich Entities im Sinne der Objektorientierung. Die weiteren Building Blocks von DDD und den wichtigen Begriff des Bounded Context schauen wir uns in der Fortsetzung dieses Blogs zur Software-Architektur an.

Advertisements

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

w

Verbinde mit %s

%d Bloggern gefällt das: