Silas Graffy

Voraussichtliche Lesedauer: 10 Minuten

Techblog

Von Schichten zu Ringen – Hexagonale Architekturen erklärt

Auf unserem diesjährigen Seminar des Bereichs IT-Sanierung habe ich, bewaffnet mit Stift und Flipchart, einen kurzen Talk zu hexagonalen Architekturen gehalten. Und da ich glaube, dass sich dieser Architekturstil viel zu langsam durchsetzt, möchte ich ihn auch hier noch einmal erläutern.

Techblog

Warum Schichten nicht genug sind …

Aber starten wir bei etwas, das wir alle gut kennen, einer klassischen Schichtenarchitektur. Ein_e Anwender_in interagiert über die UI-Komponente mit der Domänenschicht einer Anwendung, die wiederrum über einen Data Abstraction Layer auf eine Datenbank zugreift.

Zum einen vermittelt eine solche Architektur ein falsches Bild: Die Datenbank als Fundament der Softwarearchitektur – die Zeiten, in denen man einen Softwareentwurf mit einer Entity-Relationship-Modellierung startete, lassen grüßen. Zum anderen besteht eine Abhängigkeit vom fachlichen Code des Domain-Layers zum technischen Code im DAL, wie wir an den Beziehungen der enthaltenen Klassen sehen (natürlich vereinfacht die Abbildung hier stark, mit nur einer Klasse je Schicht):

Diese Abhängigkeit zwingt uns oft, unseren fachlichen Code auch dann anzupassen, wenn sich eigentlich nur technische Infrastruktur geändert hat – und das passiert bei langlebigen Softwaresystemen typischerweise sehr viel häufiger als grundlegende fachliche Änderungen. Aber auch umgekehrt lassen sich eben solche fachlichen Weiterentwicklungen sehr viel einfacher umsetzen, wenn dabei keine externen Abhängigkeiten – zu technischer Infrastruktur oder sonstigem – bestehen.

… und was man dagegen tun kann

Was macht also der kluge Software Crafter oder die kluge Software Crafterin, die Domain und DAL voneinander entkoppeln möchte? Er oder sie extrahiert ein Interface (sowieso eine gute Idee bzgl. Tests und Mocking):

Nun hängt der fachliche Code im Domain Layer zwar nicht mehr von der technischen Implementierung in der DAL-Klasse ab, auf der Ebene der Komponentenabhängigkeiten hat sich aber (noch) nichts geändert. Abhilfe schafft hier das Dependency Inversion Principle (DIP; Robert C. Martin erklärt im als fiktives Streitgespräch gehaltenen Blog-Artikel A Little Architecture sehr schön dessen Auswirkung auf Softwarearchitekturen):

Wichtig bei diesem Schritt der Abhängigkeitsumkehr ist es, darauf zu achten, das Interface nicht bloß zu verschieben, sondern es wirklich zum Teil des Domain Models zu machen, z. B. indem Methoden fachlich statt technisch benannt werden. In jedem Fall ergeben sich nun die folgenden Komponentenabhängigkeiten:

Innen und außen statt oben und unten

Nun haben die wenigsten Softwaresysteme keine weiteren Schnittstellen außer User Interface und Datenbank (als Persistenzmedium). Oft gibt es zusätzlich noch APIs, um die Funktionalität der Software anderen Systemen zur Verfügung zu stellen (z. B. mittels REST Gateway), Logging in Dateien oder einen anderen Speicher, ggf. auch E-Mail-Benachrichtigung bei bestimmten Ereignissen, und und und … Ordnet man den hierfür nötigen Code – wo nötig unter Berücksichtigung des DIP – ebenfalls in Schichten um den Domain Layer an, ergibt sich eine Architektur ähnlich dieser:

Alistair Cockburn begann Mitte der 1990er Jahre damit, diese Art der Architektur mit einem Hexagon zu visualisieren:

Da die sechs Seiten mehr oder weniger willkürlich erschienen, erfolgte 2005 die Umbenennung zu Ports and Adapters. In dieser Nomenklatur stehen die ins Innere der Anwendung (Domain) gewanderten Interfaces für die Ports, während deren Implementierungen im äußeren Sechseck eine Adapterfunktion zwischen Anwendungskern und Benutzer_innen, Datenbanken, Logdateien, Fremdsystemen etc. zukommt.

Schichten 2.0

Trennt man sich nun von der Darstellung als Sechseck und unterscheidet gleichzeitig etwas feiner zwischen Innen und Außen, ergibt sich die 2008 von Jeffrey Palermo vorgeschlagene Onion-Architektur.

Sie startet im Inneren mit einem Domain Model, das frei von allen Abhängigkeiten die fachlichen Bausteine der Anwendung beschreibt. Der Domain Services Ring darum beinhaltet fachliche Logik, die sich über mehrere Elemente des Domänenmodells erstreckt. Er ist ausschließlich vom Domain Model abhängig und bildet gemeinsam mit diesem die gesamte Fachlichkeit ab. Diese wird genutzt von den Application Services, die anwendungsspezifische Logik wie z. B. Rechtesteuerung, implementieren. Schließlich liegen Infrastructure, User Interfaces und APIs und auch Tests in einer Schicht darum.

Abhängigkeiten verlaufen dabei immer von Außen nach Innen und niemals anders herum. Das betrifft neben Code-Abhängigkeiten natürlich auch Datenformate. Beispielsweise sollte eine Zeichenkette, deren Format von einem externen System definiert wird, niemals in einer Schicht weiter innen als Infrastructure interpretiert werden. Stattdessen definieren die inneren Schichten Formate – oder besser eigene Datentypen –, auf die die Zeichenkette durch den Adapter in der Infrastrukturschicht abgebildet wird.

Robert C. Martins Clean Architecture von 2012 kann als Derivat der Onion-Architektur mit abweichenden Bezeichnern betrachtet werden.

Als Vorteile dieses Architekturmuster lassen sich zusammenfassen:

  • Die Fachlogik kann unabhängig von Infrastruktur kompiliert, deployed und wiederverwendet werden.
  • Die Anwendungslogik kann von verschiedenen UIs, Batches, Daemons/Diensten und Tests gleichermaßen genutzt werden.
  • Der Anwendungskern bleibt unabhängig von der Außenwelt.
  • Der Austausch von Persistenzmechanismen je nach Deploymentszenario ist problemlos möglich.
  • Fachlicher und technischer Code sind konsequent voneinander getrennt.

Microservices, Self-contained Systems und Domain-Driven Design

Auch zu Microservices und Self-contained Systems (SCS) erscheint die Onion-Architektur als natural fit. Jeder Service bzw. jedes SCS ist in Form seiner eigenen Zwiebel implementiert. Aus Sicht eines Systems sind alle anderen Systeme Teile der Außenwelt und dürfen als solche keine Abhängigkeiten der inneren Systemschichten darstellen.

Im Domain-Driven Design (DDD) implementiert jede Zwiebel einen Bounded Context, jeweils mit eigener Ubiquitous Language. Eine Context Map beschreibt ihre Beziehungen. Je nach Beziehungstyp nötiger Übersetzungscode lässt sich, z. B. in Form eines Anticorruption Layer, als eigene Zwiebelschicht implementieren, denn die o. g. Schichten dürfen selbstverständlich bedarfsweise erweitert werden. Mit der “Entities” (nicht zu verwechseln mit Entities im DDD) genannten firmenweiten Fachlogik beschreibt Robert C. Martin in seiner Clean Architecture die Umsetzung des DDD-Konzepts Shared Kernel. Die Application und Domain Services-Schichten beinhalten Elemente, die auch im DDD Services und Repositories genannt werden, Domain Model enthält DDD-Entities, Value Objects und Aggregate Roots.

Fazit

Der beschriebene Architekturstil, der je nach Quelle mal Hexagonal, Ports and Adapters, Onion oder Clean Architecture genannt wird, ist eine konsequente Weiterentwicklung von Schichtenarchitekturen durch Dependency Inversion. So bleibt fachlicher Code im Anwendungskern unabhängig von technischem Code in UIs, Tests, Infrastruktur etc.

Genau wie Domain-Driven Design bieten sich hexagonale Architekturen immer dann an, wenn langlebige Softwaresysteme mit aufwendiger Fachlichkeit entworfen und nachhaltig realisiert werden sollen. Für Wegwerfsoftware, Prototypen, einfache CRUD-Systeme und andere Anwendungsfälle, in denen oft Rapid Application Development zum Einsatz kommt, erscheint der erforderliche Architekturinvest jedoch nicht angemessen.


Über den Autor

Silas Graffy

IT-Sanierung 

Silas stieß 2015, nach 15 Jahren Produktentwicklung & Forschung, zu MaibornWolff. Schwerpunkte des Informatikers aus Leidenschaft bilden Software-Architekturen, agile Softwareentwicklung und System- & Architektur-Audits. Software- und Code-Qualität sind ihm ebenso ein Herzensanliegen wie Teamkultur und Collective Ownership. Er hat Abschlüssse in angewandter und Wirtschaftsinformatik. In der Freizeit gelten für ihn die wichtigen 4C: Code, Coffee, Cocktails — and Climbing.