
Stephan Schneider ist Lead Software Engineer bei MaibornWolff. Er begeistert sich für menschenlesbaren Code in Test und Produktion. Sein Ziel ist es, die Übersetzung von Anforderungen in Features so nachhaltig und nachvollziehbar wie möglich zu gestalten. Sein Herz schlägt daher für moderne Programmiersprachen wie Kotlin, die funktionale Programmierkonzepte ohne viel Boilerplate oder Design-Patterns unterstützen, sowie für Software-Architekturen, die technische und fachliche Aspekte sauber voneinander trennen. Auch unter Feature-Druck sollen Testbarkeit und Wartbarkeit gewahrt bleiben.
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.
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“
Layered Architecture | Ports and Adapters |
---|---|
Presentation | Primary Adapters |
– | Primary Ports |
Logic | Core Logic |
– | Secondary Ports |
Data | Secondary Adapters |
Ports and Adapters — Core Logic
Onion Architecture
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.
Ü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).
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.
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“. Hierbei scheint mir die Aufteilung in treibende und getriebene Hälften der Architektur sinnvoll, also in Program Adapters, Ports und Services sowie Language 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 Program und Language zusammenfassen. Damit Program Adapters und Language Adapters nicht versehentlich als Teil dieser neuen Program-Schicht bzw. Language-Schicht verstanden werden, bevorzuge ich die Bezeichner Presentation und Interpretation.
In diesem Sinne: Happy DDD-ing!
„Best of DDD Architecture“ | Ports and Adapters | Onion Architecture | Layered Architecture |
---|---|---|---|
Presentation (Program Adapters) | Primary Adapters | User Interface | Presentation |
Program Ports | Primary Ports | – | – |
Program Services | Core Logic (Primary „Core Services“) | Application Services | Logic |
Domain | Domain Model | Application Services | Logic |
Language Services | Core Logic (Secondary „Core Services“) | Domain Services | Logic |
Language Ports | Secondary Ports | – | – |
Interpretation (Language 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