---
title: 'Hexagonale Architektur: Robuste Software dank Schnittstellen statt Schichten'
source: 'https://youtube.com/watch?v=7GRG1mDVj8s'
video_id: '7GRG1mDVj8s'
date: 2026-06-15
duration_sec: 2121
---

# Hexagonale Architektur: Robuste Software dank Schnittstellen statt Schichten

> Source: [Hexagonale Architektur: Robuste Software dank Schnittstellen statt Schichten](https://youtube.com/watch?v=7GRG1mDVj8s)

## Summary

This video explains hexagonal architecture (Ports & Adapters) as a way to build maintainable software by isolating business logic from technical infrastructure. It contrasts this approach with the traditional layered architecture, which often degrades into tightly coupled code. The speaker demonstrates how ports and adapters enable independent testing and swapping of infrastructure components.

### Key Points

- **Software Architecture Definition** [00:00] — Software architecture is the division of software into components, their arrangement, properties, and interactions.
- **Goal of Software Architecture** [01:24] — Robert Martin: 'The goal of a software architecture is to minimize the human resources required to build and maintain the required system.' The speaker's definition: 'A good architecture allows software to be changed with consistently low effort over its entire lifetime.'
- **Problems with Layered Architecture** [03:36] — Layered architecture often degrades: shortcuts bypass layers, leading to high coupling, difficult testing, and inability to upgrade libraries (e.g., EclipseLink).
- **Hexagonal Architecture Goals** [07:15] — Three goals: 1) Application can be driven equally by users, other apps, or tests. 2) Business logic isolated from databases and other infrastructure. 3) Infrastructure components easily swappable.
- **How Hexagonal Architecture Works** [09:15] — The core contains business logic with no technical details. It defines ports (interfaces). Adapters connect to ports and communicate with infrastructure. The core knows only ports, not adapters or infrastructure.
- **Primary vs Secondary Ports/Adapters** [13:57] — Primary (driving) ports/adapters control the application (e.g., web, REST). Secondary (driven) ports/adapters are controlled by the application (e.g., database, mail).
- **Dependency Rule** [15:23] — All source code dependencies must point inward toward the application core. This isolates the core from technical details. Achieved via Dependency Inversion Principle.
- **Two-Way Mapping** [18:54] — To avoid technical dependencies in the core, separate domain objects (e.g., User) from infrastructure objects (e.g., JpaUser, UserDto). Mapping libraries like MapStruct can be used.
- **Testability** [24:18] — Hexagonal architecture enables isolated testing: core tested with test doubles on secondary ports; adapters tested with test doubles on primary ports or real infrastructure (e.g., Testcontainers).
- **Benefits: Changeability, Loose Coupling, Independent Development** [28:50] — Infrastructure (libraries, database) can be upgraded without changing core code. Modules have clear responsibilities. Teams can work on different components independently.
- **Drawback: Extra Effort** [33:20] — Hexagonal architecture requires more upfront work (modules, ports, adapters, mapping). Not worthwhile for simple microservices, but beneficial for large enterprise applications.

### Conclusion

Hexagonal architecture (Ports & Adapters) effectively achieves the goals of maintainability, loose coupling, and testability by isolating business logic from infrastructure. While it introduces extra complexity, it pays off for larger projects where long-term flexibility is critical.

## Transcript

Bevor wir in die hexagonale 
Architektur einsteigen,  
möchte ich mit euch drei Fragen klären. Erstens, 
was ist überhaupt Softwarechitektur? Zweitens,  
was sind die Ziele einer Software Architektur? 
Und drittens, wie schneidet die allseits  
beliebte Schichtenarchitektur im Hinblick 
auf diese Ziele ab? Beginnen wir mit der  
ersten Frage. Was ist Software Architektur? 
Nehmen wir mal an, wir wollen eine webbasierte  
Dienstleistung zur Verfügung stellen. 
Dann haben wir zuerst einmal die User,  
die benutzen einen Webbrowser und der greift über 
HTTP auf die Software zu. Und in der Software  
haben wir verschiedene Komponenten, die zum einen 
untereinander kommunizieren und zum anderen mit  
externen Komponenten wie einer Datenbank oder 
der Schnittstelle zu einem Drittanbietersystem.  
Wir könnten auch eine App zur Verfügung stellen. 
Auch die kommuniziert mit dieser Software. Dafür  
brauchen wir vielleicht eine weitere Komponente, 
und die App in sich hat natürlich auch wieder eine  
Architektur. Die Architektur, das ist also 
die Aufteilung der Software in Komponenten,  
die Anordnung und die Eigenschaften 
dieser Komponenten und die Art und Weise,  
wie diese Komponenten miteinander interagieren. 
Was ist das Ziel einer Software Architektur? Eine  
gute Antwort auf diese Frage hat Robert Martin 
in seinem Buch Clean Architecture gegeben: "The  
goal of a software architecture is to minimize the 
human resources required to build and maintain the  
required system." Und weiter: "If that effort is 
low and stays low throughout the lifetime of the  
system, the design is good." Wenn wir anfangen, 
das auf Deutsch zu übersetzen, dann klingt das so:  
"Das Ziel einer Software Architektur ist es, den 
Personalaufwand für die Erstellung und Wartung des  
gewünschten Systems zu minimieren." Das hört sich 
an, als hätte es ein Jurist geschrieben. Deshalb  
hier meine Definition: "Eine gute Architektur 
erlaubt es Software über ihre gesamte Lebensdauer  
mit gleichbleibend geringem Aufwand zu ändern." 
Aufwand könnt ihr in der Regel gleichsetzen mit  
Kosten. Eure Arbeit- oder Auftraggeber:innen 
möchten Software mit möglichst geringen und  
langfristig planbaren Kosten ändern können. Und 
Änderungen, das sind zum einen die Umsetzung von  
Userwünschen, aber auch wenn die User gerade 
gar keine Wünsche haben, sind oft Änderungen  
nötig. Z. B. wenn Sicherheitslücken in einer 
Drittanbieterkomponente entdeckt werden und die  
Komponente aktualisiert oder ausgetauscht werden 
muss. Oder wenn Drittanbieter ihre Schnittstellen  
ändern. Ich war mal in einem Projekt, da haben 
wir Login über Facebook angeboten, und ich glaube,  
ich habe alle 6 Monate eine E-Mail von Facebook 
mit Ankündigungen von Schnittstellenänderungen  
bekommen. Oder auch wenn sich Gesetze ändern. 
Viele von euch mussten sicherlich schon mal  
Software an die DSGVO anpassen. Das Ziel muss 
es also sein, um es noch mal mit den Worten von  
Bob Martin auszudrücken: "To keep Software soft." 
Und das erreichen wir, indem wir die Anwendung in  
Komponenten aufteilen, die möglichst gekoppelt 
sind und unabhängig voneinander entwickelt und  
getestet werden können. Ein gutes Beispiel ist das 
Internet. Es besteht aus Millionen von Servern,  
die relativlose gekoppelt sind und die sich 
im Laufe der Jahre und Jahrzehnte unabhängig  
voneinander mit gleichbleibend überschaubarem 
Aufwand modernisieren oder ersetzen lassen.  
Wie versuchen wir das in der Regel bei Software 
umzusetzen? Mit der Schichtenarchitektur. Die  
kennen wir alle. Wir haben hier in der 
Regel die Präsentationsschicht mit der  
Benutzerschnittstelle, die Anwendungsschicht mit 
der Geschäftslogik und die Datenhaltungsschicht  
mit z. B. einer relationalen Datenbank und 
einem OR-Mapper wie Hibernate. In vielen  
Projekten bleibt es allerdings nicht lange bei 
dieser sauberen Trennung. Oft beginnt es damit,  
dass wir unter Zeitdruck ein Feature umsetzen 
müssen, und dazu greifen wir ausnahmsweise von  
der Präsentationsschicht unter Umgehung der 
Geschäftslogik auf die Datenhaltung zu - oder  
auch einfach direkt auf Hibernate - oder aus der 
Geschäftslogik heraus auf Hibernate. Das ist sogar  
erlaubt. Das nennt sich dann "offene" oder "nicht 
strenge" Schichtenarchitektur. Und dann wollen wir  
unsere Anwendung irgendwann erweitern. Vielleicht 
wollen wir ein externes System anbieten. Dafür  
setzen wir eine Client Library in die Logikschicht 
und kommunizieren ausschließlich über die  
Logikschicht mit dem externen System. Aber dann 
müssen wir doch wieder schnell etwas umsetzen und  
greifen - natürlich nur vorübergehend - aus der 
Präsentationsschicht direkt auf diese Library zu.  
Und eine App wollen wir auch noch unterstützen. 
Dazu bauen wir ganz sauber eine REST-API, die nur  
auf die Logikschicht zugreift. Aber dann sind da 
ja noch diese nützlichen Validierungsfunktionen  
in der Präsentationsschicht, die wir auch in der 
REST-API gut gebrauchen können. Und dann kommt  
da wieder dieses Feature, das schnellstmöglichst 
fertig werden muss. Und wir umgehen ausnahmsweise  
noch einmal die Logikschicht. Und wenn wir 
jetzt noch direkt auf Hibernate zugreifen  
oder den Drittanbieter-Client, dann fällt das 
auch niemandem mehr auf. Und plötzlich ist von  
unserer anfangs so sauberen Architektur nicht mehr 
viel zu sehen. Die Schichtengrenzen sind verwischt  
und alle bauen Code dort an, wo es ihnen gerade 
passt. Das führt zu einer extrem hohen Kopplung.  
Die Komponenten lassen sich nicht mehr isoliert 
voneinander testen und wir brauchen plötzlich  
Tools wie Powermock. Powermock zu benutzen, das 
ist so, als würdet ihr Risse im Fundament eures  
Hauses mit Farbe überstreichen. Ihr versteckt 
grundlegende Mängel in der Architektur,  
anstatt sie zu beheben. Und irgendwann stürzt das 
Haus ein. Wenn wir hier Hibernate auf die nächste  
Major-Version upgraden wollen und es Änderungen an 
der API gibt, dann müssen wir Code in all diesen  
Schichten anpassen. In einem meiner letzten 
Projekte wurde eine uralte EclipseLink-Version  
verwendet und die konnten wir aus genau diesem 
Grund nicht aktualisieren. Wir hätten alle  
Schichten anpassen müssen, und das hätte Wochen 
gedauert. Und deshalb hat das Management das  
Update immer wieder herunterpriorisiert. Und 
so konnten wir nie neue JPA-Features nutzen,  
und wir konnten auch viele andere Libraries nicht 
aktualisieren, weil deren Dependencies sich mit  
denen der alten EclipseLink-Version überschnitten 
haben. Wenn die Software erstmal so aussieht,  
dann ist sie viel viel anfälliger für Bugs, und 
der Aufwand für Fixes und Änderungen schießt in  
die Höhe - und damit auch die Kosten. Das muss 
natürlich nicht so sein. Ich sage hier nicht,  
dass die Schichtenarchitektur grundsätzlich 
schlecht ist. Ich habe viele Projekte gesehen,  
die erfolgreich damit umgesetzt wurden. Aber 
die Gefahr ist eben sehr hoch, dass sich eine  
anfangs saubere Schichtenarchitektur zu einer 
unsauberen, verworren Architektur entwickelt,  
wie eben dieser hier. Alister Cockburn hat 2005 
in einem Blogartikel eine Softwarechitektur  
vorgestellt, die genau diese Probleme 
eliminieren soll: die hexagonale Architektur.  
Aus diesem Blogartikel lassen sich drei Ziele 
herauslesen. Erstens, die Anwendung - hier  
als Hexagon dargestellt - soll gleichermaßen von 
Nutzerinnen und Nutzern, anderen Anwendungen oder  
automatisierten Tests gesteuert werden können. 
Für die Geschäftslogik, die im Kern der Anwendung  
sitzt, soll es keinen Unterschied machen, ob 
sie beispielsweise von einem User Interface,  
einer REST-API oder einem Test-Famework 
aufgerufen wird. Der Kern der Anwendung  
soll also von steuernden Komponenten, von 
steuernder Infrastruktur isoliert sein. Zweitens,  
die Geschäftslogik im Anwendungskern soll isoliert 
von der Datenbank und sonstiger Infrastruktur wie  
z. B. Mailversand und Drittanbieter-API entwickelt 
und getestet werden können. Aus Sicht der  
Geschäftslogik soll es keinen Unterschied machen, 
ob Daten z. B. in einer relationalen Datenbank,  
einer NoSQL-Datenbank oder serialisiert im File 
System gespeichert werden. Der Anwendungskern soll  
also auch von gesteuerter Infrastruktur isoliert 
sein. Und drittens, Infrastrukturkomponenten  
sollen einfach austauschbar sein. Also z. B. eine 
Aktualisierung der Datenbank oder der ORM-Library,  
die Anpassung an geänderte externe Schnittstellen 
oder eine Neuimplementierung des User Interfaces.  
Eine Modernisierung der Infrastruktur soll also 
ohne Änderungen an der Geschäftslogik möglich  
sein. Das ist ein bisschen so wie Autofahren. 
Wir können Straßen erneuern, Straßenführungen  
ändern oder Verkehrsschilder austauschen, ohne 
dass ihr deswegen ein neues Auto braucht. Das sind  
die Ziele. Wie kann die hexagonale Architektur 
diese Ziele erreichen? Wie funktioniert sie?  
Dieses Hexagon stellt den Kern der Anwendung dar. 
Der Kern enthält in der Regel Modellklassen und  
die Geschäftslogik der Anwendung. Hier gibt es 
keinerlei technische Details wie REST-Controller  
oder JPA-Repositories. Wir haben hier rein 
fachlichen Code. Und hier ist die Infrastruktur.  
Die Komponenten der Infrastruktur habe 
ich erstmal als Fragezeichen dargestellt,  
denn wenn wir die Geschäftslogik entwickeln, 
dann wissen wir eventuell noch gar nicht,  
welche Infrastrukturkomponenten wir benötigen. Wie 
kann der Kern nun mit der Außenwelt kommunizieren?  
Der Kern definiert Schnittstellen, die 
sogenannten Ports und an diese Ports  
schließen wir die sogenannten Adapter an, und die 
wiederum kommunizieren mit den Komponenten der  
Infrastruktur. Und jetzt kommt der entscheidende 
Punkt: Die Geschäftslogik kennt ausschließlich die  
Ports. Die Adapter und die Infrastruktur dahinter 
sind für sie nicht sichtbar. Das könnt ihr  
vergleichen mit einem Mischpult. Es hat Eingänge, 
an die ihr z. B. ein Mikrofon, ein Keyboard oder  
eine E-Gitarre anschließen könnt, und es hat 
Ausgänge, an die ihr Lautsprecher, Kopfhörer  
oder ein Aufnahmegerät anschließen könnt. Aber 
wie diese Geräte intern funktionieren, das spielt  
für das Mischpult keine Rolle. Und genau aus 
dieser Analogie stammt die Bezeichnung "Port".  
An diese Ports könnt ihr jedes Gerät anschließen, 
das den mechanischen und elektrischen Protokollen  
des Ports entspricht. Das war jetzt erstmal 
ziemlich abstrakt, deshalb zeige ich euch als  
nächstes ein ganz konkretes Beispiel. Wir beginnen 
wieder mit dem Kern der Anwendung. Die Anwendung  
soll eine Benutzerregistrierung entgegennehmen. 
Dafür definieren wir einen Registrierungsport.  
Wie die Anwendung die Registrierung entgegennimmt, 
ob über eine Webseite, eine Desktopanwendung  
oder eine REST-API ist für die Entwicklung der 
Geschäftslogik erstmal irrelevant. Dann soll die  
Anwendung die Benutzerdaten irgendwo speichern. 
Dafür definieren wir einen Persistenzport. Wo  
und wie die Daten gespeichert werden, ist 
erstmal nicht wichtig. Und dann wollen wir  
noch eine Willkommensnachricht verschicken. Dafür 
definieren wir einen Versandport. Und auch wie  
genau wir die Nachrichten verschicken, ob über 
einen lokalen oder öffentlichen Mailserver oder  
einen Unified-Messaging-Anbieter, auch das 
spielt aus Sicht der Anwendung keine Rolle.  
An diese Ports können wir jetzt Adapter 
anschließen. Für die Registrierung können  
wir eine Webseite implementieren, über die sich 
Benutzerinnen und Benutzer über ihren Webbrowser  
anmelden können. Persistieren können wir 
die Daten in einer SQL-Datenbank. Dafür  
könnten wir einen JPA-Adapter z. B. mit Hibernate 
implementieren. Und zum Versand der Nachrichten  
benutzen wir unseren firmeninternen Mailserver, 
den wir mit einem SMTP-Adapter anbinden. Für die  
Registrierung wollen wir neben der Webseite auch 
noch eine REST-API anbieten. Dafür können wir an  
unseren bestehenden Eingabeport einfach noch einen 
zweiten Adapter anschließen, einen REST-Adapter,  
auf den dann externe Anwendungen zugreifen 
können. Was genau passiert nun in diesen Ports  
und Adaptern bei der Registrierung? Dazu werde 
ich einmal diesen linken Teil heranzoomen. Die  
Nutzerin oder der Nutzer füllt im Browser ein 
Formular aus und klickt auf "Absenden". Der  
Browser schickt daraufhin die Formulardaten 
als POST-Request an den Web-Adapter. Der  
Adapter wertet den POST-Request aus und ruft eine 
`registerPort.registerUser()` Methode auf. Genauso  
könnte eine externe Anwendung die Nutzerdaten 
als JSON-Paket an den REST-Adapter schicken.  
Der wertet das JSON-Paket aus und ruft wieder die 
gleiche `registerUser()`-Methode auf, die auch der  
Web-Adapter aufgerufen hat. Schauen wir einmal auf 
die andere Seite - auf diesen rechten Teil. Um die  
Nutzerin oder den Nutzer zu speichern, ruft die 
Geschäftslogik eine `saveUser()`-Methode auf dem  
Persistance Port auf. Der JPA-Adapter generiert 
für diesen Aufruf ein SQL-Kommando und schickt das  
an die Datenbank. Wie genau der JPA-Adapter das 
SQL generiert - ob er dafür einen StringBuilder  
einsetzt oder Hibernate oder EclipseLink - und 
welche Version dieser Libraries er verwendet - das  
spielt aus Sicht des Anwendungskerns keine Rolle. 
Der Kern ist vollständig vom Adapter isoliert. Wie  
diese Isolierung funktioniert, zeige ich euch 
gleich. Ich zoome wieder heraus. Und wie euch  
sicher aufgefallen ist, gibt es zwei Arten 
von Ports und Adaptern: Auf der linken Seite  
haben wir solche, die die Anwendung steuern, 
und auf der rechten Seite haben wir solche,  
die von der Anwendung gesteuert werden. Die 
Steuernden nennen wir primäre oder treibende Ports  
und Adapter und die gesteuerten bezeichnen wir 
als sekundäre oder getriebene Ports und Adapter.  
Durch die Ports und Adapter werden Anwendungskern 
und Infrastruktur an dieser Grenze voneinander  
isoliert. Technische Details wie JPA, Hibernate, 
RESToder JSON sind nur in den Adaptern sichtbar,  
nicht im Anwendungskern. Der Kern weiß nicht, 
welche Technologien und Libraries wir außen  
herum verwenden, und es spielt für ihn auch 
keine Rolle. Das ist so wie bei dem Mischpult  
vorhin und z. B. der E-Gitarre. Für das Mischpult 
spielt es keine Rolle, aus welchem Material die  
Gitarre gebaut ist, welche Saiten aufgezogen 
sind oder wer die Gitarre spielt. Jetzt habe  
ich heute schon sehr oft das Wort "Isolierung" 
genannt. Isolierung, das hört sich erstmal gut an,  
und das lässt sich auf Bildern auch schön 
darstellen. Doch wie funktioniert das in  
der Praxis? Wie lässt sich Isolierung in 
der Software, im Quellcode realisieren?  
Das möchte ich euch an einem Klassendiagramm 
zeigen. Das hier ist unser Anwendungshexagon.  
Darin haben wir die Implementierung der 
Userregistrierung im `RegisterUserService`.  
Die öffentliche Schnittstelle dieses Services 
definieren wir außerdem in einem Interface im  
`RegisterUserPort`, und der Service implementiert 
dieses Interface. Auf dieses Interface greift dann  
ein REST-Controller zu, und der benutzt dann z. 
B. RESTEasy. Wenn wir den Code jetzt auf zwei  
Module aufteilen, z. B. zwei Maven-Module, dann 
haben wir hier ein Application- Modul und hier  
ein Controller-Modul. Das Controller-Modul hat 
Abhängigkeiten auf das Application-Modul und auf  
RESTEasy. Und da die Dependency von Controller 
zu Application geht und nicht anders herum,  
hat das Application-Modul auch keine Dependency 
auf RESTEasy. Der Code im Application-Modul weiß  
also nichts über die Technologie, die 
im Controller-Modul eingesetzt wird.  
Auf der anderen Seite haben wir unseren 
JPA-Adapter mit einer Dependency auf  
Hibernate. Wie kann jetzt der Anwendungscode im 
`RegisterUserService` den JPA-Adapter aufrufen?  
Wenn wir hier eine Dependency einrichten, dann 
hat das Application-Modul auch eine transitive  
Dependency auf Hibernate - und damit nicht 
nur das Application-Modul, sondern auch das  
Controller-Modul. Und genau diesen Rattenschwanz 
an Dependencies wollen wir bei der hexagonalen  
Architektur vermeiden. Wir wollen zwar von hier 
innen Code da draußen aufrufen, aber wir wollen  
keine Dependency in diese Richtung und deshalb 
drehen wir die Dependency um. Wir invertieren  
sie, und zwar mit dem "Dependency Inversion 
Principle", dem D aus SOLID. Und das geht so:  
Wir setzen in das Application Hexagon 
noch ein Interface, den `PersistancePort`.  
Der Service hat eine Abhängigkeit auf das 
Interface, und der JPA-Adapter implementiert  
das Interface und wird per Dependency Injection 
mit dem Service verbunden. Und damit geht  
zwar der Aufruf von innen nach außen, aber die 
Quellcodeabhängigkeit von außen nach innen. Und  
damit hat der Anwendungskern keine Abhängigkeit 
auf den JPA-Adapter und auch keine indirekte  
Abhängigkeit auf Hibernate. Der Kern hat also kein 
Wissen über irgendwelche technischen Details. Er  
kennt weder RESTEasy noch JPA oder Hibernate. All 
diese technischen Details liegen ausschließlich  
außerhalb des Kerns in den Adaptern. Alle 
Abhängigkeiten zeigen von außen nach innen  
zum Anwendungskern. Und das ist ein grundlegendes 
Prinzip der hexagonalen Architektur, und das hat  
auch einen Namen: Dependency Rule. Die Dependency 
Rule besagt, dass alle Quellcodeabhängigkeiten von  
außen Richtung Anwendungskern gerichtet sein 
müssen. Und damit ist die Dependency Rule wie  
ein Schutzschild für den Anwendungskern: 
Sie schirmt den Kern vor allen technischen  
Einflüssen von außen ab. Hier seht ihr noch mal 
die Gesamtarchitektur. Alle Abhängigkeiten führen  
zum Kern. Und das ermöglicht die Isolierung, die 
die hexagonale Architektur so mächtig macht. Aber  
es macht auch mehr Arbeit. Und damit kommen wir 
zum Thema "Mapping". Wenn wir mit JPA arbeiten,  
dann sieht unsere User-Klasse in der Regel 
irgendwie so aus. Die hat annotierte Felder  
und entsprechende Abhängigkeiten zu JPA. Wo 
setzen wir diese Klasse hin? Schauen wir noch  
einmal auf das Klassendiagramm, und jetzt zoomen 
wir in diesen rechten Teil hinein. Wenn wir unsere  
User-Klasse hier in den Anwendungskern setzen, 
dann brauchen wir für die Annotationen eine  
Abhängigkeit vom Kern auf Hibernate. Die haben 
wir aber gerade erst mühsam durch die Dependency  
Inversion entfernt und wir haben gelernt, dass 
die Dependency Rule, eines der grundlegenden  
Prinzipien der hexagonalen Architektur, genau das 
nicht erlaubt. Wenn wir die User-Klasse hier raus  
in das Adapter-Modul setzen, dann liegt zwar die 
Abhängigkeit auf Hibernate innerhalb des Adapters,  
aber der `RegisterUserService` muss die 
User-Klasse ja auch kennen, und damit  
haben wir wieder die Dependency Rule verletzt. Um 
dieses Dilemma aufzulösen, gibt es verschiedene  
Mapping-Strategien. Ich werde an dieser Stelle 
nur eine dieser Strategien vorstellen, die meiner  
Erfahrung nach in der Regel sinnvollste, und 
zwar das Zwei-Wege-Mapping - oder auf englisch:  
Two-Way Mapping. Am Ende der Präsentation findet 
ihr einen Link zu einem Artikel, in dem ich auch  
einige andere Mapping-Strategien beschreibe. Beim 
Two-Way Mapping gibt es zwei User-Klassen: eine  
im Anwendungskern und eine im Adapter. Die heißt 
jetzt `JpaUser`, um nicht mit der User-Klasse im  
Kern verwechselt zu werden. Die `JpaUser`-Klasse 
sieht so aus, also so wie die User-Klasse,  
die ich vorhin gezeigt habe. Die hat annotierte 
Felder, Dependencies auf JPA und in der Regel  
noch Getter und Setter. Die User-Klasse im Kern 
hingegen sieht so aus: Die hat ähnliche Felder,  
aber keine JPA-Annotationen. Dafür z. B. einen 
Konstruktor, der die Parameter validiert und  
dann ein neues gültiges User-Objekt erzeugt mit 
den erforderlichen Feldern, wie hier z. B. auch  
dem Registrierungsdatum. Also alles rein fachliche 
Angelegenheiten. Und wenn wir mit einem reichen  
Domainmodell arbeiten, dann hat diese Klasse 
auch noch Methoden, mit denen wir den Zustand des  
User-Objekts ändern können. Wir haben also diese 
zwei User-Klassen und jetzt ist es die Aufgabe des  
JPA-Adapters, beim Aufruf der `saveUser()`-Methode 
das User-Objekt aus dem Anwendungsmodell auf ein  
`JpaUser`-Objekt zu mappen und dieses dann über 
Hibernate zu persistieren. Der JPA-Adapter hat  
natürlich auch noch eine `loadUser()`-Methode, 
die lädt ein `JpaUser`-Objekt über Hibernate,  
mappt dieses auf ein Anwendungs-User-Objekt und 
gibt das dann an den aufrufenden Code aus dem  
Kern zurück. Als Code sieht das etwa so aus: Hier 
die save-Methode, die mappt das User-Objekt auf  
ein `JpaUser`-Objekt und persistiert das dann 
- hier z. B. über ein Spring Data Repository.  
Und die load-Methode sieht so aus: Sie lädt das 
`JpaUser`-Objekt über das Spring Data Repository  
und mappt das dann auf einem `User`-Objekt bzw. 
ein `Optional` davon. Diesen Mapper, den müsst  
ihr nicht selbst implementieren. Dafür gibt es 
Libraries wie z. B. MapStruct und ModelMapper.  
Wenn ihr googelt, findet ihr noch ein paar andere 
wie JMapper, Dozer und Orika, aber die werden seit  
einigen Jahren nicht weiterentwickelt. So ein 
Mapping ist nicht nur beim JPA-Adapter sinnvoll,  
sondern auch beim REST-Adapter. Und dafür 
zoomen wir einmal in diesen Bereich hinein.  
Hier ist unsere `User`-Klasse - und oft wollen 
wir nicht alle Attribute einer Entity über die  
REST-API sichtbar machen. Bei der `User`-Klasse 
z. B. könnten wir ein internes Feld verstecken  
wollen oder die ID der Person, die diese Notiz 
gemacht hat. Außerdem müssen wir für Datums- und  
Zeitfelder definieren, wie diese in dem JSON-Paket 
formatiert sein sollen. Eine `User`-Klasse für den  
REST-Controller könnte z. B. so aussehen: Die 
benutzt eine JSON-Annotation, um den Timestamp  
zu formatieren und hat dementsprechend eine 
Abhängigkeit beispielsweise auf Jackson.  
Und auch diese technische Abhängigkeit wollen 
wir nicht im Anwendungskern haben. Also - hier  
wieder das Klassendiagramm - also platzieren 
wir das `UserDto` zusammen mit der JSON-Library  
außerhalb des Kerns in das Controller-Modul. 
Und der REST-Controller mappt nun in der  
`registerUser()`-Methode das `UserDto`-Objekt 
auf ein `User`-Objekt. Und der Controller hat  
natürlich auch eine `getUser()`-Methode, 
und in der mappt er das `User`-Objekt aus  
dem Anwendungskern auf ein `UserDto`. So 
ein Mapping ist übrigens nicht nur bei  
der hexagonalen Architektur sinnvoll - auch die 
Schichtenarchitektur kann davon profitieren. Aber  
bei der hexagonalen Architektur ist es zwingend 
notwendig, um die technischen Abhängigkeiten  
aus dem Kern fernzuhalten und gleichzeitig 
die Dependency Rule nicht zu verletzen. Und  
jetzt kommen wir zu einem der großen Vorteile 
der hexagonalen Architektur, der Testbarkeit.  
Am Anfang habe ich über die Ziele einer 
Softwarearchitektur gesprochen, und da  
habe ich als eine der Anforderungen isoliert 
testbare Komponenten genannt. Und in der Tat  
macht es die hexagonale Architektur sehr einfach, 
Komponenten isoliert zu testen. Den Anwendungskern  
können wir ganz einfach testen, indem wir ihn 
über einen primären Port aufrufen und den oder  
die vom Test betroffenen sekundären Ports mit 
Test Doubles verbinden. Das Test Double soll  
in seiner Rolle als Spy die Aufrufe aus dem Kern 
protokollieren und in der Rolle als Stub Antworten  
des Adapters simulieren. Der Test verifiziert 
dann die Antwort des Anwendungskerns und die  
protokollierte Interaktion mit dem Adapter. Wir 
können nicht nur die Geschäftslogik isoliert von  
den Adaptern testen, sondern auch andersherum die 
Adapter isoliert von der Geschäftslogik. Hier ist  
der REST-Adapter. Testen können wir den z. B. mit 
REST Assured. Das schickt einen HTTP-Request an  
den Adapter, und der REST-Adapter ruft dann ein 
Test Double des primären Anwendungsports auf. Das  
Test Double soll wieder die Aufrufe protokollieren 
und eine Antwort des Anwendungskerns simulieren.  
Und der Adapter schickt dann eine Antwort zurück 
an den Test. Dort kann dann wieder die Antwort  
verifiziert werden und die Interaktion mit dem 
Anwendungskern. Hier ist der JPA-Adapter. Hier  
können wir von unserem Test aus mit Testcontainers 
eine Datenbank hochfahren und mit Testdaten  
füllen. Dann ruft der Test den Adapter auf und 
der sendet eine SQL Query an die Datenbank. Die  
Datenbank schickt ihre Antwort an den JPA-Adapter, 
und der schickt eine Antwort an den Test. Im Test  
können wir wieder die Antwort verifizieren und 
die erwarteten Änderungen in der Datenbank.  
Neben diesen Integrationstest sollten wir 
natürlich unabhängig von der Architektur  
auch immer End-to-End-Tests schreiben. 
Wenn wir den Blick noch einmal auf die  
gesamte Anwendung werfen, dann können all diese 
Komponenten isoliert voneinander getestet werden.  
Genauso wie auch E-Gitarre, Keyboard, Mikrofon, 
Mischpult, Kopfhörer, Lautsprecher und Recorder  
unabhängig voneinander getestet werden können. 
Wo wir hier gerade noch einmal das komplette  
Hexagon sehen - Alistair Cockburn wurde in einem 
Interview gefragt: "Warum eigentlich ein Sechseck?  
Hat das irgendeine besondere Bedeutung?" Und 
Cockburns Antwort war überraschend pragmatisch:  
Er wollte eine Form verwenden, die noch keiner 
verwendet hat. Vierecke werden überall verwendet,  
Fünfecke sind schwer zu zeichnen, also wurde es 
ein Sechseck. Eine weitere Frage, die oft gestellt  
wird, ist: "Was ist der Unterschied zwischen 
hexagonaler Architektur und Ports & Adapters?"  
Die Antwort ist ganz einfach. Es gibt keinen. 
Beide Begriffe bezeichnen dieselbe Architektur.  
Der offizielle Name, den Alister Cockburn seiner 
Architektur gegeben hat, ist "Ports & Adapters".  
Die Bezeichnung "hexagonale Architektur" ergibt 
sich aus der grafischen Darstellung, die ihr heute  
viele Male gesehen habt. Alistair Cockburn hat in 
dem Interview, das ich eben erwähnt habe und zu  
dem ich euch nachher noch einen Link mitgebe, 
verraten, dass auch er den bildlichen Namen,  
also "hexagonale Architektur" vorzieht, aber 
dass der offizielle Name einer Architektur  
einer sein muss, der deren Eigenschaften 
beschreibt. Und das tut "Ports & Adapters"  
eben besser als "hexagonale Architektur". Und 
damit haben wir die hexagonale Architektur - oder  
die Ports-and-Adapters-Architektur von 
allen Seiten betrachtet. Wir haben den  
Anwendungskern mit der Geschäftslogik und ohne 
jegliche technischen Details. Wir haben Ports,  
die im Code einfach Interfaes sind. Wir haben 
Adapter, in denen die technischen Details liegen  
und die wir an diese Ports anschließen. Primäre 
Adapter rufen primäre Ports auf. Sekundäre Adapter  
implementieren sekundäre Ports. Wir haben die 
Infrastruktur, mit der die Adapter kommunizieren,  
und wir haben unseren Schutzschild, die Dependency 
Rule, die festlegt, dass alle Abhängigkeiten  
Richtung Kern zeigen müssen. Und jetzt möchte ich 
uns die Ziele einer Softwarearchitektur noch mal  
in Erinnerung rufen und abgleichen, inwieweit die 
hexagonale Architektur diese Ziele erfüllt. Hier  
sind noch mal die Ziele: Software soll leicht 
änderbar sein, und das soll sie während ihrer  
gesamten Lebensdauer bleiben. Und das erreichen 
wir, indem wir sie in lose gekoppelte Komponenten  
strukturieren, die unabhängig entwickelbar 
und testbar sind. Gehen wir die Kriterien mal  
im Einzelnen durch. Änderbarkeit. Wir können die 
Geschäftslogik im Kern unserer Anwendung ändern,  
ohne dass wir die Adapter oder die Infrastruktur 
ändern müssen, solange wir die Ports unverändert  
lassen. In der Praxis geht natürlich eine Änderung 
der Geschäftslogik auch oft mit Änderungen am  
User Interface und den Daten einher, sodass das 
zugegebenermaßen nicht das Killerargument ist.  
Viel wichtiger ist, dass wir die Adapter und auch 
die von ihnen benutzten Libraries wie RESTEasy,  
Jackson, Hibernate, aber auch die 
Komponenten der Infrastruktur wie  
z. B. die Datenbank aktualisieren oder komplett 
austauschen können, ohne dass wir auch nur eine  
Zeile Code in der Geschäftslogik ändern 
müssen. Wenn wir hier z. B. Hibernate  
auf die nächste Major Version upgraden wollen 
und das Upgrade Änderungen im Code erfordert,  
dann ist davon ausschließlich der JPA-Adapter 
betroffen, denn der ist die einzige Komponente,  
die eine Abhängigkeit auf Hibernate hat. Um noch 
mal die Auto-Analogie aufzugreifen: Wenn ihr eure  
Reifen wechseln wollt, dann braucht ihr vielleicht 
andere Felgen, aber ihr braucht keine neue Achse,  
kein neues Getriebe und keinen neuen Motor. Am 
Ende der Präsentation verlinke ich euch eine  
Artikelserie, in der ich demonstriere, dass selbst 
das Application Framework ein austauschbares  
technisches Detail der Infrastruktur sein kann. 
Und zwar ersetze ich dort Spring durch Quarkus,  
ohne auch nur eine Zeile Code im Anwendungskern 
zu ändern. Noch ein großer Vorteil, den ich unter  
Änderbarkeit einsortiere, ist der folgende: 
Und zwar könnt ihr mit der Entwicklung des  
Anwendungskerns beginnen und definiert erstmal nur 
die Interfaces für die Ports und testet gegen die,  
ohne dass ihr euch Gedanken über die Adapter 
machen müsst - oder über die Technologien,  
die ihr dahinter einsetzen wollt. Also ob 
RESToder GraphQL, ob Spring Data oder Panache,  
ob SQL oder NoSQL. Und so könnt ihr die 
Entscheidung über die Technologien in  
der Infrastruktur so lange hinauszögern, bis 
ihr bei der Entwicklung der Geschäftslogik so  
viel Erfahrung über eure Anwendung gesammelt 
habt, dass ihr viel besser entscheiden könnt,  
welche Technologien am besten geeignet sind. 
Also z. B. in welcher Art von Datenbank sich  
eure Daten am besten speichern lassen. Kommen 
wir zum zweiten Punkt, der losen Kopplung.  
Eine lose Kopplung erreichen wir zum einen ganz 
klar durch die Isolierung zwischen den Ports und  
den Adaptern und zum anderen dadurch, dass wir die 
Komponenten auf separate Module aufteilen können,  
wie hier z. B. Maven Module. Fachliche Themen 
werden ausschließlich im Anwendungsmodul  
implementiert und alle technischen Themen in einem 
oder mehreren Adaptermodulen. Die Abhängigkeiten  
zwischen den Modulen definieren wir entsprechend 
der Dependency Rule. Und so stellen wir sicher,  
dass keine technischen Abhängigkeiten in den Kern 
gelangen und dass wir alle Verantwortlichkeiten  
eindeutig im Code lokalisieren können. Denn jetzt 
müssen wir beim Anlegen einer neuen Klasse genau  
überlegen, in welches Modul die Klasse gehört. 
Denn wenn wir sie ins falsche Modul legen,  
dann erreichen wir entweder von der neuen Klasse 
aus nicht den Code, den wir aufrufen wollen,  
oder wir erreichen die neue Klasse nicht von dem 
Code aus der sie aufrufen soll. Und genau dieses  
"neuen Code mal eben irgendwo dran bauen", das ist 
etwas, zu dem die Schichtenarchitektur mit ihren  
zahllosen transitiven Dependencies leider 
regelrecht verführt. Der dritte Punkt ist  
unabhängige Entwicklung. Wenn der Anwendungskern 
mit seinem Port einmal definiert ist, dann kann  
die weitere Arbeit an den Komponenten sehr gut 
z. B. auf mehrere Pairs aufgeteilt werden. Und  
viertens, die unabhängige Testbarkeit. Das habe 
ich euch vor ein paar Minuten erst gezeigt. Wir  
können durch den Einsatz von Test Doubles alle 
Komponenten vollständig isoliert voneinander  
testen. Die hexagonale Architektur erfüllt damit 
alle Kriterien einer guten Softwarearchitektur.  
Solltet ihr also nur noch mit der hexagonalen 
Architektur arbeiten? Nein. Die hexagonale  
Architektur hat natürlich auch einen Nachteil. 
Das braucht ihr nicht zu lesen. Das ist ein  
Screenshot aus meiner IDE - aus einem Projekt 
mit einem Anwendungsmodul, drei Adaptermodulen  
und außerdem noch einem separaten Modellmodul 
und einem Bootstrap-Modul. Die Aufteilung des  
Codes in Module, das Implementieren der Ports und 
der Adapter und des Mappings stellt einen enormen  
Mehraaufwand dar. Wenn ihr in jedem dieser 
Module nachher nur eine Klasse liegen habt,  
z. B. bei einem einfachen Microservice, der eine 
einzige Entity in die Datenbank schreibt und aus  
ihr ausliest, dann lohnt sich dieser Mehraaufwand 
nicht. Aber für eine große Enterprise-Anwendung,  
wo jedes dieser Module nachher hunderte 
von Klassen enthält, da kann sich der  
Aufwand schnell amortisieren, und ihr könnt 
langfristig Aufwand und Kosten sparen. Gut ist,  
wenn ihr eine Person im Team habt, die schon erste 
Erfahrung mit der hexagonalen Architektur gemacht  
hat und die abschätzen kann, ob sich dieser 
Meeraufwand für euer Projekt lohnt. Oder noch  
besser: Entwickelt einfach mal selbst eine kleine 
Anwendung nach hexagonaler Architektur, sammelt  
damit erste Erfahrung, und dann könnt ihr die 
Person sein, die bei eurem nächsten Projekt die  
hexagonale Architektur ins Spiel bringt. Und damit 
sind wir am Ende angekommen. Hinter dem QR-Code  
findet ihr die versprochenen Links und die Slides, 
und wenn ihr an mehr Content von mir interessiert  
seid (seltener über Architektur, eher über 
neue Java Features), dann schaut gerne mal bei  
HappyCoders.eu vorbei oder bei einem der anderen 
Kanäle. Ich danke euch für eure Aufmerksamkeit.
