Clean Architecture vs. Onion Architecture vs. Hexagonale Architektur

Es gibt drei Architekturmodelle, die immer wieder genannt und angewandt werden:

  • Hexagonale Architektur (Ports and Adapters)
  • Onion Architecture
  • Clean Architecture

In unseren Trainings wird immer wieder diskutiert, wo die Unterschiede liegen: Clean Architecture vs. Onion Architecture – welche ist besser?

Im Folgenden wird beleuchtet, worin sich die drei Ansätze unterscheiden und wo die Gemeinsamkeiten liegen. Im Anschluss folgt eine kritische Betrachtung der Nachteile und zuletzt wird mit der IODA Architektur eine Alternative aufgezeigt.

Entwicklung der Architekturmodelle

Hexagonale Architektur, 2005

Die Hexagonale Architektur wurde 2005 von Alistair Cockburn beschrieben. Sie ist auch unter dem Namen Ports and Adapters bekannt, da dies das zentrale Konzept der Architektur darstellt. Die Kernidee dieser Anwendungsarchitektur ist es, die Abhängigkeit in ihrer Richtung von außen nach innen zu richten. Im Kern des Hexagons/Sechsecks liegt die Anwendungslogik. Diese stellt den wichtigsten Aspekt eines Softwaresystems dar. Es ist fundamental, dass diese leicht automatisiert getestet werden kann. Folglich wäre es sehr hinderlich, wenn fachlicher Code abhängig wäre von bspw. Datenbankzugriffen.

Abbildung 1 Hexagonale Architektur clean architecture vs. onion architecture
[Abbildung 1: Hexagonale Architektur]

Es ergeben sich in der Darstellung innere Schichten und äußere Schichten. Je weiter wir nach außen gehen, desto technischer wird die Verantwortlichkeit. Im Kern der Anwendung liegt das Domain Model, verantwortlich für die Fachlichkeit einer Anwendung. Diese Logik soll unabhängig von technischem Code sein. Also muss die Aufteilung der Verantwortlichkeiten dafür sorgen, dass bspw. das User Interface außen liegt und nach innen abhängig ist von anderen Aspekten wie Domain Services oder Application Services. Wenn wir auf diese Weise unseren Code unterteilen und implementieren können wir sicherstellen, dass die Business Logic im Kern unabhängig ist von User Interface und Datenbank.

Die Umkehr der Abhängigkeiten erreicht die Hexagonale Architektur durch die Einführung von sogenannten Ports und Adapters. Ein Adapter ist eines der bekanntesten Entwurfsmuster. Immer dort, wo ein innerer Aspekt eine Dienstleistung eines äußeren Aspekts benötigt, stehen wir vor der Frage, wie wir mit der Abhängigkeit umgehen. Um eine Umkehr zu erreichen, definiert der innere Aspekt einen sogenannten Port. Softwaretechnisch ist dies ein Interface. Über das Interface definiert der innenliegende Aspekt, welche Dienste er vom außenliegenden Aspekt benötigt.

Da häufig die äußeren Aspekte nicht modifiziert werden können, sorgen die Adapter dafür, dass ein äußerer Aspekt einen Port implementiert. Nehmen wir an, die Ebene der Application benötigt Dienste eines Frameworks. Das Framework betrachten wir als 3rd Party Abhängigkeit, für die wir nicht verantwortlich sind. Insbesondere können wir den Quellcode nicht so modifizieren, dass er den Port (ein Interface) implementiert. Daher realisiert ein Adapter die Anbindung: auf der einen Seite implementiert der Adapter den Port. Damit kann die Application den Adapter verwenden, weil er das Interface realisiert. Auf der anderen Seite verwendet der Adapter das Framework, um darüber die Dienste bereit zu stellen.

Letztlich handelt es sich bei dieser Vorgehensweise um Dependency Inversion. Somit basiert die Hexagonale Architektur auf dem Dependency Inversion Principle (DIP).

Onion Architecture

Im Jahr 2008 hat Jeffrey Palermo den Begriff der Onion Architecture geprägt. Die wichtigsten Unterschiede zur Hexagonalen Architektur liegen in der Benennung. Ferner macht die Onion Architecture keine explizite Aussage mehr dazu, wie softwaretechnisch gesehen die Abhängigkeitsrichtung erreicht werden soll. Doch auch hier ist klar, dass die Abhängigkeiten von außen nach innen verlaufen. Es handelt sich also ebenfalls um eine DIP-basierende Architektur, mit der eine hohe Code-Qualität erreicht werden soll.

 

Abbildung 2 - Onion Architecture
[Abbildung 2: Onion Architecture]

Clean Architecture, 2012

Als letzter kam dann Bob C. Martin 2012 um die Ecke und hat einen weiteren Begriff für dasselbe Modell geprägt: die Clean Architecture. Letztlich wird hier nichts Neues geliefert. Auch er verwendet wieder leicht andere Begriffe. Doch im Kern ist die Clean Architecture wiederum das gleiche wie die Onion oder Hexagonale Architektur: DIP-basierend. Die Abhängigkeiten verlaufen von außen nach innen. Die technischen Details wie bspw. Persistenz liegen außen, die fachliche Logik innen.

Clean Architecture
[Abbildung 3: Clean Architecture]

Kritische Würdigung

Zunächst ist festzuhalten, dass die drei oben skizzierten DIP-basierenden Architekturen einen sehr wichtigen Beitrag im Sinne von Wandelbarkeit und Korrektheit leisten. Erst durch die Dependency Inversion, die Umkehr der Abhängigkeiten, sind wir in der Lage, automatisierte Tests zu schreiben, bei denen einzelne Einheiten freigestellt werden. Dazu verwenden wir Attrappen, die statt realer Implementationen im Test zum Einsatz kommen können. So lässt sich die Geschäftslogik automatisiert testen, ohne dass bspw. eine Datenbank für den Test zur Verfügung stehen muss. Gerade für den Anwendungskern ist das ein wesentlicher Fortschritt, weil wir seine Korrektheit nun in Unit Tests sicherstellen können. Eine Datenbankanbindung ist im Test nicht mehr erforderlich, was die Testbarkeit vor allem des Anwendungskerns deutlich verbessert.

Gleichzeitig sind automatisierte Tests, die auf Attrappen basieren, vergleichsweise aufwendiger zu realisieren und in Stand zu halten. Durch die Attrappen werden sehr häufig Interna offengelegt, die bei einer Änderung der internen Implementation einen Test scheitern lassen.

Zum anderen führen das Dependency Inversion Principle (DIP) und die Clean Architecture dazu, dass es in Projekten häufig mit den Interfaces übertrieben wird. Plötzlich wird es zum Standard, dass jeder Klasse ein Interface zugeordnet wird. Ob dieses tatsächlich benötigt wird, wird oft nicht hinterfragt. Schnell können solche Muster also zum Selbstzweck werden.

Nicht zuletzt werden durch die Möglichkeit, einzelne Einheiten über Attrappen isoliert testen zu können, die Integrationstests vernachlässigt. Wird die Anwendung ausschließlich durch Unit Tests mit Attrappen getestet, ist nicht sichergestellt, dass das Zusammenwirken dieser Einheiten korrekt funktioniert. Es braucht immer eine gewisse Menge an echten Integrationstests, gegen die reale Datenbank, gegen die reale Hardware, etc. Abhilfe schaffen können hier Docker Container und das TestContainers Projekt, über das ich an anderer Stelle bereits geschrieben habe.

Unterm Strich entsteht bei Anwendung der Clean Architecture häufig zu viel Struktur, die für die Erreichung von Wandelbarkeit und Korrektheit nicht zwingend erforderlich ist.

Zwischenfazit

Halten wir zunächst fest, dass die drei genannten Architekturen letztlich das selbe sind, auch wenn teils unterschiedliche Begriffe für die verschiedenen Aspekte verwendet werden. Eine Gegenüberstellung der Begriffe ist in diesem Blog-Artikel von maibornwolff zu finden.

Die Kernidee der DIP-basierenden Architekturen besteht darin, die Abhängigkeiten von außen nach innen zu führen. Dies erfüllen alle drei Modelle. Dieser Architekturstil geht davon aus, dass die Abhängigkeiten über Interfaces und Dependency Inversion sowie Dependency Injection aufgelöst werden.

Nun könnte man sich damit zufriedengeben, dass eine solche Softwarearchitektur ein wichtiger Schritt in die richtige Richtung darstellt. Allerdings stellt sich die Frage, ob nach so vielen Jahren der Anwendung nicht Alternativen entwickelt wurden. Und in der Tat, die gibt es: die IODA-Architektur.

Integration Operation Data API Architecture, 2015

Die IODA-Architektur wird seit etwa 2015 von Ralf Westphal und dem Autor, Stefan Lieser, beschrieben, angewandt und unterrichtet. Sie unterscheidet sich von den drei anderen genannten dadurch, dass eine andere Lösung für das Problem der Abhängigkeiten verwendet wird: die Trennung von Integration und Operation.

Betrachtet man das Dependency Inversion Principle, stellt man fest, dass auch nach Einführung eines Interfaces noch eine Vermischung von Aspekten vorliegt. In der folgenden Abbildung ist ersichtlich, dass die Funktionseinheit A nach wie vor für zwei Dinge verantwortlich ist, Sie kümmert sich einerseits um das, was sozusagen draufsteht: A macht, was A machen soll. Heißt die Klasse bspw. Product, enthält sie die Logik zum Thema Produkte. So weit so gut. Darüber hinaus integriert die Klasse allerdings auch. Sie ruft nämlich Funktionen aus dem ProductRepository auf. Das macht sie, angeleitet durch das DIP, zwar über den Umweg eines Interfaces. Aber es bleibt dabei, dass die Klasse Product damit einerseits für die Fachlichkeit zuständig ist, andererseits für die Integration des Repositories.

Abbildung 4 SRP Verletzung trotz DIP clean architecture vs. onion architecture
[Abbildung 4: SRP Verletzung trotz DIP]

Die IODA-Architektur basiert auf dem Integration Operation Segregation Principle (IOSP). Es handelt sich also um eine IOSP-basierende Architektur. Dabei liegen die Aspekte auf der mittleren Ebene nebeneinander. Dies ist ein fundamentaler Unterschied zu den DIP-basierenden Architekturen, bei denen die Aspekte geschichtet sind. Die IODA-Architektur ist somit keine Schichtenarchitektur, auch wenn das Bild den Eindruck erwecken mag. Innerhalb der Ebene der Operationen gibt es untereinander keine Abhängigkeiten. Das Zusammenwirken wird erreicht durch die darüber liegenden Integrationen. Auf diese Weise sind die Verantwortlichkeiten klar getrennt: entweder eine Funktionseinheit enthält irgendeine Form von Logik, dann nennen wir sie Operation. Das kann Domänenlogik sein, aber auch Datenbank- oder Ui-Logik.

Abbildung 4 IODA Architektur clean architecture vs. onion architecture
[Abbildung 5: IODA-Architektur]

Verwendet eine Funktionseinheit andere Funktionseinheiten, ist ihre Verantwortlichkeit die Integration. Dann enthält sie keinerlei Logik. Als Ergebnis haben wir es bei den Operationen mit Einheiten zu tun, die isoliert getestet werden können, ohne dass dabei Attrappen zum Einsatz kommen. Die Operationen sind bereits isoliert. Somit entfällt die Notwendigkeit von Dependency Inversion und Interfaces. Auf der anderen Seite haben wir mit den Integrationen Einheiten vorliegen, auf denen die Integrationstests ansetzen können. Da bei einem Integrationstest die realen Einheiten integriert werden, sind auch hier keine Interfaces und Dependency Inversion erforderlich.

Fazit

Wir machen seit vielen Jahren sehr gute Erfahrung bei der Anwendung der IODA-Architektur. Sie passt ganz wunderbar zur Testpyramide und unterstützt somit eine Teststrategie bestehend aus System-, Integrations- und Unit Tests optimal. Dabei können wir auf viele Interfaces verzichten und haben es im Test nur sehr selten mit Attrappen zu tun. Das IOSP ist eine Weiterentwicklung des DIP und bietet viele Vorteile beim Testen und beim Verständnis einer Codebasis. Probiere doch mal aus, eine Beispielanwendung in der IODA-Architektur zu realisieren.

Unsere Seminare

course
Clean Code Developer Basics

Prinzipien und Tests – Das Seminar wendet sich an Softwareentwickler, die gerade beginnen, sich mit dem Thema Softwarequalität auseinanderzusetzen. Es werden die wichtigsten Prinzipien und Praktiken der Clean Code Developer Initiative vermittelt.

zum Seminar »
course
Clean Code Developer Advanced

Mit Flow Design von den Anforderungen zum Clean Code – Lernen Sie mit Flow Design einen Softwareentwicklungsprozess kennen, der Sie flüssig von den Anforderungen zum Clean Code führt.

zum Seminar »
course
Clean Code Developer Trainer

Seminare als Trainer durchführen – Dieses Seminar wendet sich an Softwareentwickler, die ihr Wissen über die Clean Code Developer Prinzipien und Praktiken bzw. über Flow Design als Trainer an andere weitergeben möchten.

zum Seminar »

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

de_DEGerman