Über unsMediaKontaktImpressum
Torben Fojuth 17. Mai 2016

Domain-Driven Design im Hexagon

Es braucht einen gemeinsamen Weg braucht, um die interne Struktur der Software nicht dem völligen Wildwuchs preiszugeben. © ProMotion / Fotolia.com
© ProMotion / Fotolia.com

Was zeichnet eine "gute" Systemarchitektur aus? Es gibt viele Antworten auf diese Frage – meine lautet: Sie erlaubt es den Entwicklern, ihren täglichen Job möglichst effektiv zu erledigen. Diesem Ziel folgend berichte ich von meinen Erfahrungen als technischer Projektleiter bei der Restrukturierung eines bestehenden Softwaresystems.

Stellen Sie sich zum Einsteig bitte folgende Situation vor: Sie übernehmen ein bestehendes Legacy-Software-Projekt ohne klar erkennbaren Architekturstil. Es gesellen sich eine Handvoll Entwickler zu Ihnen und Sie müssen gemeinsam versuchen, das System nicht nur zu warten, sondern ihm neue Features hinzuzufügen. Schnell wird dabei klar, dass es einen gemeinsamen Weg braucht, um die interne Struktur der Software nicht dem völligen Wildwuchs preiszugeben.

Mein Team und ich entschieden uns dafür, das System sukzessive in eine hexagonale Architektur zu überführen. Gleichzeitig modellierten wir die Geschäftslogik nach dem sogenannten Domain-Driven Design. Aufgrund meiner Projekterfahrungen der letzten drei Jahre mit dieser Architektur stelle ich folgende These auf: Der Einsatz von Domain-Driven Design zusammen mit hexagonaler Architektur beantwortet die alltäglichen Fragen der Entwickler auf konkrete und einfache Weise.

In diesem Artikel möchte ich Ihnen zunächst die beiden oben genannten Begriffe näher bringen und Sie abschließend davon überzeugen, dass meine These zutrifft. 

Domain-Driven Design

Im Jahre 2003 stellte Eric Evans in seinem Buch "Domain-Driven Design: Tackling Complexity in the Heart of Software" [1] einen Weg der Software-Modellierung vor, den er Domain-Driven Design (kurz DDD) nannte. Zehn Jahren später sorgte Vaugn Vernon ("Implementing Domain-Driven Design" [2]) meiner Meinung nach für eine Renaissance des Themas. 

Ubiquitous Language

Ein Grundgedanke des DDD ist, dass die Software die gesprochene Sprache des Kunden möglichst direkt abbildet. Das heißt beispielsweise, wenn ein Kunde aus der Logistik intern von "Verlegung von Containern an Standorte" spricht, so sollte es im Code eine Stelle wie

container.verlegeAn(Standort) 

geben. Um dies zu ermöglichen, muss zunächst diese sogenannte Ubiquitous Language, also die einheitliche Sprache, im Zusammenspiel mit den jeweiligen Experten der Kundendomäne ermittelt werden. Eine Erkenntnis dabei ist, dass es unmöglich scheint, Begriffe zu finden, die im gesamten Unternehmen Gültigkeit haben. Schlimmer noch: Begriffe besitzen je nach Abteilung unterschiedliche Bedeutungen. Tritt dieser Fall ein, so liegen offenbar unterschiedliche Subdomains vor. Lassen Sie mich Ihnen dazu ein Beispiel aus dem eCommerce geben. 

  • Beispiel: Artikel
    Angenommen, Sie betreten die Website eines Onlineversandhändlers und verwenden die Suchfunktion um einen bestimmten Artikel zu finden. Das Ergebnis dürfte ziemlich sicher eine Liste von Artikeln sein, die Ihrem Suchbegriff mehr oder weniger entsprechen. Zu diesem Zeitpunkt ist man seitens des Onlinehändlers daran interessiert, Ihnen als Kunden möglichst viele relevante Artikel zu präsentieren. Sie sollen die Möglichkeit haben, Ihre Suche zu verfeinern und Ihnen sollen artverwandte Artikel empfohlen werden. Was zu diesem Zeitpunkt des Kaufprozesses weniger interessiert, sind Fragen wie: aus welchem Lager ein bestimmter Artikel geliefert oder ob eine Geschenkverpackung gewünscht wird. Beim Betreten des Warenkorbs und dem anschließenden Bestellvorgang werden diese Fragen durchaus relevant.
    Falls es nun verschiedene Abteilungen gibt, die sich um die jeweiligen Phasen Ihres Einkaufs kümmern, so kann man sich vorstellen, dass der Begriff des Artikels für diese Abteilungen unterschiedliche Bedeutungen hat. Im DDD spricht man von unterschiedlichen Subdomains. Kommt es zur Implementierung des Artikels für die jeweiligen Subdomains, so würde man zwei getrennte Artikel mit verschiedenen Funktionalitäten modellieren.

Wir sind nun also auf der Ebene der Technik angekommen. Idealerweise implementiert man  Subdomains in verschiedenen Softwaremodellen – im DDD als BoundedContext bezeichnet – um den unterschiedlichen Bedeutungen der Begriffe Rechnung zu tragen. Zur Modellierung stehen im DDD vor allem die nachfolgend erklärten fünf Bausteine zur Verfügung.

Entities

Im DDD stellen Entities die zentralen Elemente der Modellierung dar. Sie repräsentieren Dinge, die auf eine eindeutige Art identifizierbar sind. So könnte beispielsweise ein Kunde mit seiner Kundennummer oder ein Artikel mit seiner Artikelnummer eine Entity darstellen. Entities besitzen eine Historie von Änderungen, die ihnen im Verlauf verschiedenster Anwendungsfälle widerfahren sind. Dies könnte eine Änderung des Preises am Artikel sein. Vor allem aber sind Entities die hauptsächlichen Träger von Geschäftslogik in Form von speziellen Methoden. Sie enthalten jegliche Geschäftslogik, die zur Durchführung eines speziellen Geschäftsprozesses notwendig ist.

  • Beispiel
    Nehmen wir an, es gibt eine Geschäftsregel, die besagt, dass ein Warenkorb niemals einen Gesamtwarenwert von 500€ überschreiten darf. Das Hinzufügen eines Artikels zum Warenkorb könnte in einer Methode wie

    warenkorb.fuegeHinzu(Artikel, Anzahl, …); 

    implementiert werden. Folglich muss innerhalb dieser Methode oben genannte Geschäftsregel berücksichtigt werden. Aber auch alle weiteren Geschäftsregeln im Kontext des Hinzufügens – wie beispielsweise die eventuelle Neuberechnung von Gutscheinen – hat innerhalb der Methode zu geschehen. Diese Form der Implementierung folgt im übrigen den Prinzipien des OOPs [3]. Im Vergleich dazu stellt das Auslesen des aktuellen Warenkorbwertes und dessen Prüfung vor dem Hinzufügen von außen ein Anti-Pattern dar.

Gleichzeitig können Geschäftsregeln direkte Auswirkungen auf die Modellierung von Entities haben. In obigem Beispiel muss die Entity Warenkorb über alle Bestellpositionen inklusive deren Preise verfügen, um die Einhaltung der Geschäftsregel gewährleisten zu können. Der Warenkorb ist in diesem Fall also eine große Entity mit baumartiger Struktur. Gäbe es obige Geschäftsregel (oder eine ähnlich lautende) nicht, so würde es ausreichen, im Warenkorb nur Referenzen (in Form von IDs) auf die Bestellpositionen zu halten. Damit wäre die Entity sehr viel handlicher und leichter zu testen.

Value Objects

Hinter dem Begriff des Value Objects verbirgt sich der Gedanke, dass es neben Entities auch Dinge innerhalb der Domain gibt, die modelliert sein wollen. Oftmals tauchen Value Objects zur Beschreibung von Eigenschaften einer Entity auf.

Ein Value Object ist ein Wertobjekt, dass in der Regel einen (oder mehrere) primitiven Datentyp kapselt, um gegebenenfalls auch darauf Geschäftslogik implementieren zu können. Diese hat oft den Charakter einer Validierung, kann aber auch anders geartet sein.

  • Beispiel: Anzahl
    Ein klassisches Beispiel für ein Wertobjekt ist eine "Anzahl", wie man sie unter anderem beim Hinzufügen von Artikeln in den Warenkorb benötigt (s. Entities). Eine Anzahl kann niemals negativ sein, was bereits zum Zeitpunkt der Instanzierung geprüft und gegebenenfalls verhindert werden kann. Somit können sich Klienten, die die Angabe einer Anzahl verlangen, sicher sein, dass der übergebene Wert valide ist. Gleichzeitig kann es Geschäftsregeln geben, die besagen, dass von einem Artikel maximal fünf Einheiten in den Warenkorb gelegt werden können. In diesem Fall könnte das Wertobjekt Anzahl auch diese Geschäftsregel sicherstellen.
  • Beispiel: Betrag
    Der Betrag eines Artikelpreises gibt ein weiteres gutes Beispiel für ein Value Object ab. Vermutlich hat es sich schon herumgesprochen, dass man generell Beträge nicht als Fließkommazahl darstellt, um so Rundungsdifferenzen zu vermeiden. Des Weiteren enthält ein Betrag aber in der Regel auch die Währung, in der er angegeben ist. Es handelt sich bei Beträgen also um Value Objects, die mehr als einen primitiven Wert kapseln. Geschäftsregeln auf Beträgen können branchenspezifische Rundungsregeln oder ähnliches sein.

Grundsätzlich stellen Value Objects zusammen mit Entities die Hauptbausteine eines Domainmodells dar. Sie übertreffen die Entities sogar in ihrer reinen Anzahl, da es in der Regel zu jeder Entity ein Value Object gibt, das dessen ID modelliert.

Application Services

Application Services stellen Anwendungsfälle der Applikation zur Verfügung. Jede Methode eines Application Service repräsentiert genau einen solchen Anwendungsfall. Sie orchestrieren die notwendigen Schritte die zum Ablauf eines Anwendungsfalls gehören und haben die Hoheit über die Frage, welche Aufgaben Teil eines speziellen Anwendungsfalls sind. Sie entscheiden jedoch nicht darüber, wie diese Aufgaben erfüllt werden. Jeder Anwendungsfall stellt im technischen Sinne eine Transaktion dar, in deren Verlauf nur ein einziger Geschäftsprozess auf einer Entitiy stattfinden sollte.

  • Beispiel
    Das Abschicken einer Bestellung in einem Online-Shop gibt ein gutes Beispiel für einen Anwendungsfall ab. So sei es Aufgabe der ApplicationService-Methode

    bestellenService.bestellungAbschliessen(WarenkorbID,KundeID,…);

    zunächst die referenzierten Entities Warenkorb und Kunde aufzulösen. Anschließend weist sie den Warenkorb an, ein Bestellungs-Objekt aus dessen inhärenten Daten zu erzeugen (was nebenbei eine gute Gelegenheit darstellt, ein Event wie BestellungAbgeschickt zu emittieren, aber darum geht es erst weiter unten.
    Neben dem Persistieren der neu erzeugten Bestellung ist es weiterhin Aufgabe des Anwendungsfalls, eine Bestellbestätigung per E-Mail zu versenden. Dazu ruft der Service ein entsprechendes Drittsystem auf. An diesem Punkte lohnt es sich, kurz über den transaktionalen Charakter einer ApplicationService-Methode nachzudenken: Soll ein Fehler beim Versenden der Bestellbestätigung dazu führen, dass der gesamte Anwendungsfall scheitert? Oder kann das Versenden der Bestellbestätigung auch nachgelagert geschehen – beispielsweise ausgelöst durch ein Domain-Event?
    Abschließend muss der Anwendungsfall vielleicht noch dem Kunden eine bestimmte Menge von Bonuspunkten gutschreiben. Hier gilt es, ähnliche Überlegungen anzustellen wie bei der Bestellbestätigung und mit dem fachlichen Experten zu klären, ob eventual consistency in Frage kommt.

Abschließend sei zur Transaktionalität von ApplicationService-Methoden noch gesagt, dass das Persistieren von eventuell geworfenen Domain-Events im Verlauf eines Anwendungsfalls unbedingt Teil der Transaktion sein muss. Nur so ist gewährleistet, dass Domain-Events und der Zustand von Entities zu jeder Zeit synchron sind.

Repositories

Jeder Anwendungsfall benötigt Zugriff auf spezielle Entities. Folglich muss es einen Weg für ApplicationServices geben, an diese Entities zu gelangen. Dies geschieht im DDD über Repositories.

Repositories stellen – im Vergleich zu artverwandten Konzepten wie den DataAccessObjects [4] – eine maximal abstrahierte Form von Behältern dar, in denen sich Entities eines Typs befinden. Idealerweise verhalten sie sich so, als wären sie einfache Collections, d. h. sie stellen Operationen wie add(), get(), remove() & contains() zur Verfügung. Gleichzeitig abstrahieren sie vollständig von der unterliegenden Implementierung. Sie übernehmen neben der Kommunikation mit der jeweiligen konkreten Technologie für die Persistierung auch die Konvertierung von Entities in die zur Speicherung notwendige Form sowie natürlich auch die Konvertierung in der entgegengesetzten Richtung.

Caching-Konzepte können innerhalb des Repositories zum Einsatz kommen. Diese sind für den Client aber transparent. Des Weiteren kann es unter dem Gesichtspunkt der Performanz sinnvoll sein, spezielle Formen der oben genannten Operationen zu implementieren. So wäre es denkbar, eine Methode zu erstellen, um alle Entities zu ermitteln, die einem gewissen Kriterium entsprechen. Denn das Abfragen aller Entities durch den Client und ein nachgelagertes Filtern ist stark inperformant. Auch in diesem Fall aber muss diese spezielle Abfrage per Kriterium so modelliert sein, dass sie keine Annahmen über die unterliegende Technologie trifft.

Domain Events

Bei der Abarbeitung von Anwendungsfällen in ApplicationServices finden Geschäftsprozesse auf Entities statt. Ein Domainexperte könnte die im Verlauf dieser Prozesse geschehenen Ereignisse für relevant genug halten, um darauf an anderen Stellen (innerhalb wie außerhalb der selben Applikation) Reaktionen zu provozieren. Es bedarf einiger Übung, diese Ereignisse beim Gespräch mit dem Domainexperten zu isolieren. Es gibt einige Signalwörter bzw. Phrasen wie "immer, wenn" oder "sobald", die die Vermutung nahelegen, dass es sich um ein relevantes Domain-Event handelt.

Domain-Events dokumentieren, dass ein bestimmtes Ereignis innerhalb der Domain stattgefunden hat. Sie enthalten dazu alle für nachgelagerte Empfänger notwendigen Daten zum Ereignis. Jedes Event wird im auftretenden System persistiert und zwar stets synchron mit eventuellen Änderungen an Entities, die das Event ausgelöst haben.

Anschließend können Events dann an andere Systeme verteilt werden – beispielsweise durch eine Message Queue oder das Bereitstellen eines Event-Feeds, der von interessierten Systemen konsumiert werden kann.

  • Beispiel: Artikeldetailseite
    Um nicht schon wieder den Warenkorb für ein Beispiel heranzuziehen, wähle ich stattdessen für dieses Beispiel ein Ereignis, das man vielleicht nicht auf den ersten Blick als solches erkennt. Und zwar spreche ich von dem Ereignis, das sich ein bestimmter Kunde die Detailseite eines Artikels angeschaut hat. Zunächst wird die Aktion durch einen Application Services modelliert. Nehmen wir an, dass im Kontext dieses Ereignisses folgende drei Dinge beachtet werden sollen:

    • Die Seitenbetrachtung soll für Business Intelligence (BI) aufgezeichnet werden;
    • Die Empfehlungsmaschine soll lernen, dass der spezifische Kunde Interesse am gesehenen Artikel hat;
    • Der Artikel soll in die kundenspezifische Historie "Zuletzt gesehene Produkte" hinzugefügt werden.

    Angenommen, die zuständigen Domain-Experten haben entschieden, dass nur das in Punkt 3 genannte Hinzufügen zur Artikelhistorie zwingend synchron mit der Betrachtung der Detailseite erfolgen muss. Für die beiden erstgenannten Punkte ist eventual consistency ausreichend, so dass die Aktualisierung asynchron erfolgt. Dazu wird ein entsprechendes Domain-Event namens

    ArtikeldetailseiteAngeschaut(ArtikelId,KundenId)

    implementiert. Die Signatur zeigt, dass dem Event zwingend sowohl die ID des Artikels der angeschaut wurde sowie die ID des betrachtenden Kunden übergeben werden muss. Jedem Domain-Event ist außerdem gemein, dass es sich intern, neben seiner eigenen ID, den Zeitpunkt seines Auftretens merkt.
    Dieses Event wird nun zum Einen dem für Empfehlungen verantwortlichen System zur Verfügung gestellt, welches daraufhin seine Prognosen und Empfehlungen für den Kunden verbessern kann. Zum Anderen liefert das Event der BI-Abteilung wertvolle Informationen über den Seitenverlauf des Kunden während seines Besuchs.

Hexagonale Architektur

Abb.1: Drei-Schichten-Architektur. © Torben Fojuth
Abb.1: Drei-Schichten-Architektur. © Torben Fojuth

Die auch als "Onion-Architektur" oder "ports and adapters" bekannte hexagonale Architektur wurde von Alistair Cockburn [5] postuliert. Cockburn beschreibt die Absicht hinter dieser Architekur selbst mit folgenden Worten: "Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases."

Es soll also eine Applikation geschaffen werden, die weder konkrete Annahmen darüber trifft wie bzw. von wem sie bedient wird (menschliche Interaktion, Skripte, etc.) noch wie die von ihr verwendeten Drittsysteme funktionieren. Wie kann man diesen Zustand erreichen?

Gehen wir zunächst von einer wie in Abb.1 gezeigten klassischen Drei-Schichten-Architektur  [6] aus. Vergleicht man die Abhängigkeiten der skizzierten Architektur mit den oben formulierten Anforderungen an eine hexagonale Architektur, so stellt man ein Problem fest: Die Logikschicht ist direkt abhängig von der darunter liegenden Datenschicht. Diese ungewollte enge Kopplung führt dazu, dass die Logikschicht nicht isoliert entwickelt und getestet werden kann.

Dependency Inversion

Abb.2: Dependency Inversion. © Torben Fojuth
Abb.2: Dependency Inversion. © Torben Fojuth

Verwendet man das Dependency Inversion Principle (DIP) [7], um diese enge Kopplung aufzulösen, erhält man die in Abb.2 gezeigte Architektur. In diesem Beispiel habe ich die zuvor nur mit Namen versehenen Schichten abstrahiert sowie Komponenten innerhalb der Schichten skizziert, um ein besseres Verständnis der internen Vorgänge zu schaffen.

Innerhalb der Präsentationsschicht liegt beispielhaft ein Controller, der den Zugriff auf die Applikation über das Web ermöglicht. Innerhalb der Logikschicht finden wir einen Service, der einen bestimmten UseCase anbietet, sowie das Interface eines Produkt-Repositories, das hier beispielhaft den Zugriff auf Produktdaten ermöglichen soll. In der Datenschicht findet sich nun eine mögliche Implementierung des Interfaces auf Basis von MongoDB.

Diese Implementierung ist jedoch vom Service nicht direkt sichtbar. Vielmehr zeigt die Abhängigkeit nun in die andere Richtung – sie wurde "invertiert" –, da die Implementierung das Interface benötigt. Erst beim Start des Systems wird dem Service die konkrete Implementierung des Interface (z. B. durch einen IoT-Container wie Spring oder Guice) übergeben.

Durch diese Art der Invertierung wird die Logikschicht vollständig unabhängig von den Drittsystemen, die erst zur Laufzeit bestimmt werden. Gleichzeitig kann sie auf verschiedene Arten aus der Präsentationsschicht heraus angesprochen werden. Damit sind die Anforderungen an eine hexagonale Architektur erfüllt.

Abb.3: Die nach DIP transfomierte 3-Schicht-Architektur. © Torben Fojuth
Abb.3: Die nach DIP transfomierte 3-Schicht-Architektur. © Torben Fojuth

Von Schichten zum Hexagon

Abb.3 zeigt abschließend eine Darstellung dieser invertierten Struktur in der namengebenden Form des Hexagons. Die Präsentationsschicht befindet sich auf der linken Seite des Hexagons. Um der Abstraktion Rechnung zu tragen, spricht man allgemein auch von den aktiven oder primären Adaptern. Sie werden von außerhalb der Applikation angesprochen und initiieren als Akteure die Ausführung von Anwendungsfällen. Beispiele für primäre Adapter sind WebController, GUIs, CLIs, MessageHandler, REST-APIs und dergleichen mehr.

Auf der rechten Seite findet sich die Datenschicht. Folgerichtig nennt man die dort befindlichen Systemteile passive oder auch sekundäre Adapter, da sie stets von der Mitte heraus angesprochen werden, aber niemals selbständig aktiv werden. Typische Vertreter sind neben der Persistenz der Versand von E-Mails oder Messages aber auch die Abfrage anderer Systeme bspw. per APIs oder Feeds. In dem gekennzeichneten inneren Bereich der Applikation ist die Logikschicht angesiedelt. Hier spricht man vom Domainbereich, also all dem was wir im Zuge des Domain-Driven Design implementiert haben, um unser Modell zu erstellen. Einzig die Application Services haben in dieser Darstellung im Vergleich zum Schichtenmodell einen gesonderten Bereich erhalten. Sie sind die Anknüpfungspunkte für die primären Adapter (es gibt keine Services im rechten Teil des Hexagons) um Anwendungsfälle auszuführen.

Das tägliche Geschäft eines Entwicklers

Eingangs habe ich die These aufgestellt, dass DDD im Zusammenspiel mit hexagonaler Architektur den täglichen Job eines Entwicklers erleichtert. Diesen Umstand möchte ich nun am Beispiel einiger Fragen aus dem täglichen Geschäft eines Entwicklers illustrieren.

Wo erstelle bzw. ändere ich einen Anwendungsfall?

Abb.4: Verortung des Anwendungsfalls. © Torben Fojuth
Abb.4: Verortung des Anwendungsfalls. © Torben Fojuth

Anwendungsfälle sind Methoden innerhalb eines Application Services. Dort und nur dort wird definiert, welche Schritte Teil eines Anwendungsfalles sind. Wie im Abschnitt Application Services bereits gesagt, findet man innerhalb der Anwendungsfälle keine Details darüber, wie die einzelnen Schritte auszuführen sind. Das heißt, es findet ausschließlich eine Orchestrierung der am Anwendungsfall beteiligten Domänenobjekte statt. Stößt man innerhalb der Methode auf Geschäftslogik, so ist dies ein klarer "code smell".

Wo erstelle bzw. ändere ich Geschäftslogik?

Abb.5: Geschäftslogik in Entities. © Torben Fojuth
Abb.5: Geschäftslogik in Entities. © Torben Fojuth

Innerhalb der Domain sind Entities und Value Objects Träger von Geschäftslogik. Sie enthalten die für jegliche Geschäftslogik relevanten Informationen. Den Tugenden des object oriented programmings folgend werden also auch die Operationen auf diesen Daten dort platziert. Wie im Teil über Hexagonale Architektur erwähnt, ist nur der mittlere Teil der Anwendung, in dem die Domäenenobjekte angesiedelt sind, ohne Abhängigkeiten. Folglich kann das Verhalten von Entities und Value Objects hervorragend durch Unittests verifiziert werden, was es umso wünschenswerter macht, die Geschäftslogik dort zu halten.

Wie greife ich auf externe Systeme zu?

Abb.6: Zugriff auf Drittsysteme. © Torben Fojuth
Abb.6: Zugriff auf Drittsysteme. © Torben Fojuth

Die Hexagonale Architektur macht die Vorgabe, von Drittsystemen zu abstrahieren. Dort habe ich bereits gezeigt, wie man die konkrete Datenbankimplementierung hinter Repositories verbirgt. Das selbe gilt allgemein für den Zugriff auf Drittsysteme: Es wird ein Interface innerhalb der Domain angelegt, das den gewünschten Zugriff des Clients auf das Drittsystem modelliert. Anschließend muss man diesen Zugriff durch eine Implementierung in einem passiven Portadapter auf der Basis der gewählten Technologie implementieren.

Sind die Abhängigkeiten korrekt?

Abb.7: Kontrolle der Abhängigkeiten. © Torben Fojuth
Abb.7: Kontrolle der Abhängigkeiten. © Torben Fojuth

Als Entwickler vergewissere ich mich ab und an, dass meine Arbeit dem gewählten Architekturstil entspricht und ich keine strukturellen Fehler gemacht habe. Die hexagonale Architektur ist in dieser Hinsicht leicht zu verifizieren. Eine Überprüfung der Import-Anweisungen meiner Dateien zeigt schnell, ob ich den gewählten Pfad verlassen habe: Abhängigkeiten müssen, wie in Abb.7 gezeigt, stets von außen nach innen zeigen. So dürfen Klassen innerhalb der modellierten Domain ausschließlich Klassen importieren, die ebenfalls innerhalb der Domain liegen. Exporte von Klassen außerhalb der Domain sind in keinem Fall zulässig.

Application Services dürfen ebenfalls nur Klassen aus der Domain verwenden. Insbesondere ist es nicht zulässig, andere Application Services zu verwenden (Anwendungsfall-Schachtelung) oder direkt auf passive Portadapter zuzugreifen.

Bei den Portadaptern unterscheidet man zwischen aktiven und passiven Adaptern. Die aktiven Portadapter verwenden Application Services oder Klassen in der direkten Nachbarschaft, wie beispielsweise zur Transformation von Domainobjekten in eine Form, wie sie im Adapter benötigt wird. Außerdem sind Domainobjekte erlaubt, die für den Aufruf eines Application Services als Parameter dienen, wie etwa die KundenID.

Bei den passiven Adaptern (auf der rechten Seite des Hexagons) dürfen keine Application Services verwendet werden. Die passiven Adapter importieren vor allem die Interfaces der Domain, die sie implementieren (s. Dependency Inversion) sowie ebenfalls Klassen, die Parameter innerhalb dieser Interfaces darstellen. Auch hier kommen Hilfsklassen zur Konvertierung hinzu, die in der Regel direkt neben den Implementierungen angesiedelt sind.

Wie ermögliche ich Zugriff auf mein System?

Abb.8: Neue Zugriffswege auf das System ermöglichen. © Torben Fojuth
Abb.8: Neue Zugriffswege auf das System ermöglichen. © Torben Fojuth

Soll anderen Systemen der Zugriff auf die Anwendungsfälle des eigenen Systems ermöglicht werden, so genügt es, einen entsprechenden aktiven Adapter zu implementieren. In der Regel benötigt man daneben weiteren Code, der dabei hilft, die Anfragen in entsprechende Objekte des jeweiligen Application Service umzuwandeln bzw. dessen Antworten zurückzutransformieren.

Auf diese Weise kann ein System, das bisher nur eine grafische Web-Oberfläche in Form von HTML zur Interaktion zur Verfügung stellte, um eine REST-Schnittstelle erweitern. Dazu implementiert der Entwickler neben dem eigentlichen Endpunkt für die Requests außerdem einen Konverter, um die im Request enthaltenen Nutzdaten in Parameterobjekte für die Methode des Application Service umzuwandeln.

Fazit

Ein Entwickler-Team, das sich an den Architekturstil der hexagonalen Architektur in Verbindung mit DDD hält, begibt sich in ein recht dogmatisches Korsett von Regeln und Begriffen. Im Gegenzug finden die Entwickler Hilfe bei der Frage, an welchem Teil des Systems sie ihre alltäglichen Arbeit verrichten müssen.

Die Begriffe des DDD und der hexagonalen Architekur geben den Teammitgliedern außerdem ein Vokabular an die Hand, um effektiv über Fragen ihr System betreffend kommunizieren zu können. Der Effekt der "Professionalisierung der Sprache" ähnelt dem der beobachtet werden kann, wenn Junior-Entwickler das erste Mal Design-Patterns lernen und in die Lage versetzt werden, komplexere Muster mit einem einfachen Begriff zu beschreiben.

Nicht zuletzt stellt natürlich die Ubiquitous Language – die gemeinsame Sprache zwischen Kunde und Entwickler-Team – einen weiteren großen Gewinn für die tägliche Kommunikation dar. Verwendet man eindeutige Begriffe, erledigt sich so manche Rückfrage über das im Ticket beschriebene Feature von allein.

Quellen
  1. E. Evans, 2003: Domain-Driven Design: Tackling Complexity in the Heart of Software
  2. V. Vernon, 2013: Implementing Domain-Driven Design
  3. Prinzipien des OOP: Martin Fowler – Tell-Don't-Ask
  4. DataAccessObjects: Oracle – Core J2EE Patterns - Data Access Object
  5. A. Cockburn, 2005: Hexagonal Architecture
  6. Wikipedia: Schichtenarchitektur 
  7. R.C. Martin, 1996: Dependency Inversion Principle
nach Oben
Autor

Torben Fojuth

Torben Fojuth arbeitet als technischer Projektleiter bei "Neuland - Büro für Informatik" in Bremen. Dort erstellt er gemeinsam mit seinem 7-köpfigen Team maßgeschneiterte eCommerce-Lösungen und berät Kunden in Sachen...
>> Weiterlesen
botMessage_toctoc_comments_929