Techblog

DDD-Architekturen im Vergleich

Von Stephan Schneider @stefichjo auf Twitter
30. Oktober 2018

Onion-Achitektur und Ports-and-Adapters-Stil — ein Vergleich

In seinem Buch "Domain-Driven Design: Tackling Complexity in the Heart of Software" (2003) präsentierte Eric Evans die taktischen Muster des DDD (Repository, Value Object, ...), ohne jedoch zu zeigen, wie sich diese software-architektonisch implementieren lassen.

Zehn Jahre später stellt Evans im Vorwort zu Vaughn Vernons Buch "Implementing Domain-Driven Design" (2013) immerhin fest, dass Alistair Cockburns Ports and Adapters (die "Hexagonale Architektur") besser für DDD geeignet ist als die Schichtenarchitektur.

"... hexagonal architecture, which has emerged as a better description of what we do than the layered architecture." — Evans

Vernon selbst verwendet jedoch in seinem Buch durchgehend Begriffe von Jeffrey Palermos Onion-Architektur. Damit liegt die Frage nahe, ob es sich hier um den gleichen Architekturstil handelt.

Dieser Frage gehe ich in diesem Blog-Artikel nach: Ich vergleiche die Grundkonzepte der Modelle Ports and Adapters und Onion-Architektur. Als drittes Modell werfe ich noch einen Blick auf Robert Martins Clean Architecture. Im Fazit fasse ich die Vorteile der drei Modelle zusammen.

Ports and Adapters oder "hexagonale Architektur"

Mit seiner Ports-and-Adapters-Architektur (2005) rückt Alistair Cockburn erstmals die Fachlichkeit einer Anwendung in den Mittelpunkt des Softwareentwurfs und degradiert somit die Datenbank zu einem Implementierungsdetail. Er legt die Logik-Schicht des Schichtenmodells (siehe Grafik links) in das Zentrum und nennt diese Core Logic (siehe Grafik rechts). Die äußeren Schichten (Presentation und Data) nennt er Adapters und entkoppelt sie von der Core Logic durch jeweils eine weitere Schicht, die Ports. Einen vom Frontend getriebenen Port und dessen Adapter nennt er primär und einen die Datenbank treibenden Port und dessen Adapter dementsprechend sekundär.

So mappen die Layer einer Schichtenarchitektur auf Ports and Adapters.

 

Layered Architecture Ports and Adapters
Presentation Primary Adapters
 - Primary Ports
Logic Core Logic
 - Secondary Ports
Data Secondary Adapters

Ports and Adapters — Core Logic

Evans' Repository-Interface und dessen Implementierung (beispielsweise eine Datenbankanbindung) lassen sich im sekundären Port und in dessen Adapter verorten. Andere taktische Muster des DDD gehören jedoch in die Core-Logic, ohne dass Evans oder Vernon Aufschluss darüber geben, wie diese zu unterteilen wäre. Denkbar wäre eine weitere Schicht bestehend aus primären und sekundären "Core Services", die sich um das "Model" legen (siehe Grafiken links und rechts). In diesem Szenario würden die sekundären Core Services die über den sekundären Port bereitgestellten Daten (beispielsweise Events) zum Model aggregieren. Sowohl das Model als auch die sekundären Core Services stünden dann den primären Core Services zur Verfügung, um die im primären Port angebotene API (beispielsweise Commands und Queries) auf die Use Cases der Applikation zu mappen.

Die Core Logic bei Ports and Adapters

Onion Architecture

2008 präsentiert Jeffrey Palermo seine Onion-Architektur, ebenfalls als Verbesserung des Schichtenmodells — übrigens ohne Bezug auf Cockburns Ports and Adapters. Rückblickend lässt sich Cockburns Core Logic aber in Palermos Architektur als Application Core wiederfinden (siehe linke Grafik). Dieser untergliedert sich, ähnlich wie in o.g. hypothetischer Core Logic bei Cockburn (siehe rechte Grafik), in zwei weitere Schichten (Application Services und Domain Services, zusammenfassend die Business Logic) mit dem Domain Model in der Mitte. Palermo unterteilt seine Business Logic jedoch nicht in primär und sekundär, sondern ordnet sie konzentrisch an.

Vergleich: der Application Core der Onion-Architektur (li) und Core Services bei Ports-and-Adapters (re)

 

Onion Architecture
(Application Core)
Ports and Adapters
(Core Logic)
Application Services Primary "Core Services"
Domain Model "Model"
Domain Services Secondary "Core Services"

 

Hieraus wird ersichtlich, dass bei Palermo die Domain Services zwischen den Application Services und dem Domain Model liegen. Glücklicherweise dürfen Application Services aber weiterhin direkt auf das Domain Model zugreifen, denn bei Palermo dürfen Schichten übersprungen werden.

"With traditionally layered architecture, a layer can only call the layer directly beneath it. This is one of the key points that makes Onion Architecture different from traditional layered architecture." – Palermo

 

Überhaupt darf jede äußere Schicht auf jede innere Schicht zugreifen, weshalb man eine Entsprechung zu Cockburns Ports, welche den Application Core isolieren würden, bei Palermo vergeblich sucht. Folgerichtig können Tests jedweder Granularität außerhalb des Application Cores platziert werden (siehe Grafik links), zusammen mit dem User Interface und der Infrastructure, welche Cockburns primärem und sekundärem Adapter entsprechen. Dies hat zur Folge, dass innere Schichten, einschließlich des Domain Models, mit äußeren Schichten, sogar mit der Infrastructure oder dem User Interface, gekoppelt werden dürfen (siehe Grafik rechts).

Onion-Architektur: Konzentrisch oder nicht?

Clean Architecture

In seiner Clean Architecture (2012) nennt Robert C. Martin ("Uncle Bob") Palermos Business Logic Business Rules, und unterteilt sie in application-specific und enterprise-wide (jeweils Palermos Application Services und Domain Services), jedoch ohne expliziten Logik-Kern, der Palermos Domain Model entsprechen würde (siehe Grafik). Dementsprechend gehören zu Martins Enterprise-Wide Business Rules sowohl Werte-Objekte (Business Objects) als auch Entities und Repository-Interfaces. Zu den Application-Specific Business Rules gehören Use Cases (bzw. Use Case Interactors) und DTOs (Request Model und Response Model). Letztere werden von Controllern und Presentern in der nächst-äußeren Schicht, den Interface Adapters verwendet bzw. implementiert. In der äußersten Schicht liegen die Frameworks & Drivers, die zum Beispiel die Repository-Interfaces der Enterprise-Wide Business Rules implementieren. Wie bei Palermo dürfen Abhängigkeiten beliebig tief ins Innere der Applikation zeigen. Diese architektonische Freiheit nennt Martin die Dependency Rule und er erlaubt außerdem beliebig viele weitere Schichten.

"The overriding rule that makes this architecture work is The Dependency Rule. This rule says that source code dependencies can only point inwards." — Martin

 

Clean Architecture nach Uncle Bob (Robert C. Martin)

Fazit: Best of DDD Architecture

Nachdem nun von den "DDD-Architekturen", die die Fachlichkeit in ihr Zentrum setzen, die drei wohl bekanntesten gegenübergestellt wurden — Ports and Adapters, Onion Architecture, Clean Architecture — sollte ersichtlich geworden sein, dass sich diese zwar sehr ähneln, aber keineswegs als synonym zueinander behandelt werden können: Cockburn kennt keine Entsprechung zu Palermos Business Logic bzw. zu Martins Busines Rules, während Palermo und Martin das Konzept von Cockburns Ports aufgegeben haben (zugunsten der Dependency Rule). Eine Architektur mit einer Kombination aus entkoppelnden Ports wie bei Cockburn einerseits und einer Business Logic wie bei Palermo andererseits, mit denen sich die taktischen Muster des DDD implementieren lassen, ist daher mein persönliches "Best of DDD Architecture" (siehe Grafik rechts). Hierbei scheint mir die Aufteilung in treibende und getriebene Hälften der Architektur sinnvoll, also in Application Adapters, Ports und Services sowie Domain Adapters, Ports und Services, um die Anwendung anschaulich zwischen Frontend und (DB-)Backend zu positionieren.

Betrachtet man die Ports als Service-Schnittstellen, lassen sich Ports und Services zu jeweils Application und Domain zusammenfassen (siehe Grafik links). Damit Application Adapters und Domain Adapters nicht versehentlich als Teil dieser neuen Application-Schicht bzw. Domain-Schicht verstanden werden, bevorzuge ich die Bezeichner Presentation und Infrastructure

Best of DDD Architecture:

Presentation/Application/Model/Domain/Infrastructure und Services, Ports and Adapters

 

In diesem Sinne: Happy DDD-ing!

"Best of DDD Architecture" Ports and Adapters Onion Architecture Layered Architecture
Presentation (Application Adapters) Primary Adapters User Interface Presentation
Application Ports Primary Ports  -  -
Application Services Core Logic (Primary "Core Services") Application Services Logic
Model Core Logic ("Model") Domain Model Logic
Domain Services Core Logic (Secondary "Core Services") Domain Services Logic
Domain Ports Secondary Ports  -  -
Infrastructure (Domain Adapters) Secondary Adapters Infrastructure Data

 

Literatur

[Eva03] Evans, Eric, Domain-Driven Design: Tackling Complexity in the Heart of Software, Addison-Wesley, 2003
[Ver13] Vernon, Vaughn: Implementing Domain-Driven Design, Addison-Wesley, 2013

Kommentare

Sehr guter Überblick. Danke. 

Noch eine Frage zur Trennung von Application und Domain, bzw. parallel dazu zur Trennung von Presentation und Infrastruktur. Auf der Ebene der Logik unterscheidet man zwischen Logik (Regeln, Invarianten usw.), die nur für genau die aktuelle Anwendung relevant sind (Application) oder für alle Anwendungen, die das Domain Model (Geschäftsobjekt, usw.) nutzen/bearbeiten.

Ich halte die parallele Einteilung von Präsentation zur Applikation und Persistenz (oder neu Infrastruktur) zu Domain aber für zu grob. Es kann in der Präsentation Teile geben, die mehrere Applikationen gemeinsam haben, z. B. Text-Darstellung (lokalisiert) von Aufzählungskonstanten; andererseits gibt es immer DB-Zugriffe, die mit der gemeinsamen Implementierung für das gesamte Modell nicht effizient sind. Typisch ist dafür die hochoptimierte Query, die mit 10 Joins eine Zahl liefert. Damit haben wir applikationsspezifischen Code in der Persistenzschicht.

Eine klare Grundstruktur, wie konzentrische Halbkreisringe, ist didaktisch hervorragend und eine gute Grundlage Noch ein paar Klarstellungen, und wir wissen: Wer macht was wo womit? 

Ein guter Punkt, danke. Offenbar ist Martins Naming bzw. Unterscheidung in application-specific und enterprise-wide nicht immer treffend, wie Sie am Beispiel der answendungsspezifischen optimierten DB-Zugriffe aufzeigen, die eben nicht unternehmensweit sind. (Vielleicht wäre ja anwendungs-agnostisch (application-agnostic) eine bessere Bezeichnung als unternehmensweit (enterprise-wide) gewesen?) Für mich persönlich dient die Unterscheidung in primär (Presentation und Application) und sekundär (Domain und Infrastructure) in erster Linie der Unterteilung der Softwarearchitektur in eine treibende und eine getriebene Hälfte. Datenbankzugriffe, ob optimiert oder nicht, gehören also in die Infrastruktur-Schicht, egal, ob sie tatsächlich unternehmensweit verwendet werden, oder sie sich bei genauerem Hinsehen als anwendungsspezifisch herausstellen, da sie in jedem Fall zur getriebenen Seite der Software-Architektur gehören.

Neuen Kommentar schreiben

Public Comment form

  • Zulässige HTML-Tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd><p><h1><h2><h3>

Plain text

  • Keine HTML-Tags erlaubt.
  • Internet- und E-Mail-Adressen werden automatisch umgewandelt.
  • HTML - Zeilenumbrüche und Absätze werden automatisch erzeugt.

ME Landing Page Question