Ports and Adapters – Die hexagonale Zwiebelarchitektur. Teil 1: Einführung

Soft­warearchitek­tur ver­fol­gt im wesentlichen zwei Ziele: die Struk­turierung von Soft­waresys­te­men und die Min­imierung von Abhängigkeit­en zwis­chen den Struk­turkom­po­nen­ten, den soge­nan­nten Bausteinen. Spätestens seit den Neun­ziger­jahren war das Mul­ti-Tier-Pat­tern, die klas­sis­che Schicht­e­nar­chitek­tur, der Stand der Kun­st. Seit eini­gen Jahren wird dieses Grund­muster zunehmend durch einen neuen De-fac­to-Stan­dard abgelöst, der als Hexag­o­nale Architek­tur bekan­nt gewor­den ist. Was hat es mit diesem mys­tis­chem Begriff auf sich, und welche Vorteile bringt dieser Architek­turstil für die Softwareentwicklung?

Im ersten Teil dieser Artikelserie geht es darum, die Begriffe zu klären und den prinzip­iellen Auf­bau ein­er Hexag­o­nalen Architek­tur zu ver­ste­hen. Doch zunächst machen wir einen kleinen Aus­flug in die Entste­hungs­geschichte dieses pop­ulären Mikroarchitekturmusters. 

Historische Entwicklung

Alistair Cockburn, 2005

Der Begriff der Hexag­o­nalen Architek­tur wurde im Jahre 2005 von Alis­tair Cock­burn geprägt, einem der Mitze­ich­n­er des Agilen Man­i­fests. Das Wort „hexag­o­nal“ beschreibt dabei lediglich die von Cock­burn gewählte grafis­che Darstel­lung der von ihm vorgeschla­ge­nen Mikroarchitektur:

Quelle: alistair.cockburn.us

Cock­burns Ansatz sieht die strik­te Tren­nung von innerem Code und äußerem Code vor, wobei innen den Anwen­dungskern meint, also den Teil des Codes, der die Geschäft­slogik enthält. Um diesen Kern herum, also außen, sind die tech­nis­chen Code­teile ange­ord­net, welche den Geschäftskern mit der äußeren Welt verbinden. Die Außen­welt, das sind beispiel­sweise Brows­er-UIs, Mobile-Apps, Mail-Clients, Daten­banken, Mes­sage-Bro­ker usw., worüber die Anwen­dung mit Benutzern oder anderen Sys­te­men Infor­ma­tio­nen austauscht.

Jeffrey Palermo, 2008

Kurze Zeit später veröf­fentlichte Jef­frey Paler­mo seinen Vorschlag zur Struk­turierung von Soft­waresys­te­men, den er Onion Archi­tec­ture nan­nte. Als Darstel­lung wählte er konzen­trische Kreise, ähn­lich den Schicht­en ein­er Zwiebel: 

Quelle: jeffreypalermo.com

Paler­mo tren­nt eben­falls tech­nis­chen von fach­lichem Code, wobei der App­lika­tionskern expliz­it weit­er unterteilt wird: das Object Mod­el fungiert als Zen­trum, um das herum sich die Object Ser­vices und Appli­ca­tion Ser­vices anord­nen. Die Object-Ser­vice-Schicht definiert dabei die tech­nis­chen Inter­faces, die vom äußeren Infra­struc­ture-Lay­er imple­men­tiert wer­den. Hier begeg­net uns das Depen­den­cy Inver­sion Prin­ci­ple (DIP).

Mike Evans, 2004

Viele Grund­la­gen der mod­er­nen Soft­wa­reen­twick­lung wur­den bere­its 2004 von Eric Evans dargelegt bzw. bestätigt. Sein Buch Domain Dri­ven Design beschreibt eine an fach­lichen Gesicht­spunk­ten aus­gerichtete Meth­ode, die einen gewis­sen inneren Auf­bau der entwick­el­ten Bausteine impliziert. Das Ziel ist auch hier, fach­lichen von tech­nis­chem Code zu trennen. 

Quelle: http://seedstack.org/docs/business/layers/

Evans schlägt eine Aufteilung nach Schicht­en mit strik­ten Ver­ant­wortlichkeit­en und definierten Abhängigkeit­en vor, wobei der Domain-Lay­er die zen­trale Geschäft­skom­po­nente darstellt. Dieser Domain Core hat selb­st keine direk­ten Abhängigkeit­en zu anderen Kom­po­nen­ten. Auch hier wieder DIP: der Domain Core definiert die Inter­faces, die vom Infra­struc­ture-Lay­er imple­men­tiert werden. 

Die Ansätze aller drei Autoren sind sich in ihrem Wesen ähn­lich. Zusam­mengenom­men definieren sie eine mod­erne Mikroar­chitek­tur, die sowohl für den altherge­bracht­en Mono­lithen als auch bei mod­er­nen Microser­vices anwend­bar ist. 

Begriffsverwirrung — Hexagonal? Domain Driven? Onion?

Die Namen Hexag­o­nale Architek­tur, Zwiebel-Architek­tur und Domain Dri­ven Design (kurz DDD) wer­den im All­t­ags­ge­brauch oft syn­onym ver­wen­det. Die Beze­ich­nung Hexag­o­nal hat sich jedoch durchge­set­zt, um dieses Architek­tur­muster zu beschreiben. Zum einen ver­mut­lich, weil es so schön eso­ter­isch klingt, ohne jedoch Küchenas­sozi­a­tio­nen zu weck­en. Zum anderen ist DDD in sein­er Gesamtheit mehr als erken­nt­nis­philosophis­che Methodik zu ver­ste­hen, ist daher viel bre­it­er gefasst und geht über die reine Struk­turierung von Soft­ware-Bausteinen hinaus.

Hexagonale Architektur: Definition der Begriffe

Ports

Ein Port definiert logisch zusam­menge­hörige Use Cas­es und die dafür benötigten Schnittstellen.

In der fol­gen­den Abbil­dung sind beispiel­sweise diese Schnittstellen definiert (Beispiele über­nom­men von Cock­burn, s.o.):

  1. Daten­ver­ar­beitung
  2. Admin­is­tra­tion
  3. Events/Notifications (nach außen) 
  4. Daten­bank­abfra­gen.
Abb.: Ports definieren die Schnittstellen zur Außenwelt

Auf der linken Seite des Hexa­gons sehen wir die aktiv­en Ports (1) und (2), während (3) und (4) auf der recht­en Seite als pas­sive Ports ange­ord­net sind. Aktive und pas­sive Ports bilden zusam­men den Infra­struc­ture Lay­er, also den Teil der Anwen­dung, der mit der Außen­welt kommuniziert.

Aktive und passive Ports

Über die aktiv­en Ports, die nach außen sicht­bar sind, wer­den Änderun­gen in der App­lika­tion angestoßen. Men­schliche Benutzer oder tech­nis­che Clients greifen über die öffentlichen Schnittstellen aktiv in den Zus­tand der Anwen­dung ein. Aktive Ports wer­den auch als Primär­ports oder Inbound Ports bezeichnet.

Dahinge­gen sind die pas­siv­en Ports nicht von außer­halb der Anwen­dung zugänglich. Über sie kann nur die Anwen­dung selb­st auf andere Sys­teme lesend oder schreibend zugreifen und die erhal­te­nen Antworten inner­halb ihrer eige­nen Geschäft­sprozesse ver­ar­beit­en. Die Ver­wen­dung dieser Ports erfol­gt somit pas­siv. Über die pas­siv­en Ports kann keine direk­te Inter­ak­tion mit der Anwen­dung erfol­gen. Pas­sive Ports sind auch als Sekundär­ports oder Out­bound Ports bekan­nt.

Port = Schnittstelle + Datentransport

Die Ports ein­er Soft­ware­an­wen­dung beste­hen aus ein­er Schnittstel­len­spez­i­fika­tion und dem tech­nis­chen Teil der Imple­men­tierung, der Daten­struk­turen in die Anwen­dung hinein und hin­aus transportiert. 

ISP – Interface Segregation Principle

Pas­sive Ports bilden nicht die kom­plette Schnittstelle des exter­nen Sys­tems ab, son­dern nur genau den Teil, der von der App­lika­tion ger­ade benötigt wird. So würde der Noti­fi­ca­tion-Port (Nr. 3 in der obi­gen Abbil­dung) lediglich den Teil der Mailserv­er-API nutzen, der zum Versenden von E‑Mails benötigt wird. Das zuge­hörige DTO enthält dann auch nur die Attribute, die tat­säch­lich für den Mail­ver­sand rel­e­vant sind. Wer­den beispiel­sweise die Felder CC und BCC nicht benötigt, wird das DTO sie nicht enthalten.

Adapters

Adapter fungieren als Ver­mit­tler zwis­chen Sys­te­men mit unter­schiedlichen Schnittstellen. 

In der abstrak­ten Welt der Soft­waretech­nik dienen Adapter dazu, Daten­struk­turen aus Fremdsys­te­men in das eigene Sys­tem zu über­führen, zu adap­tieren, und zwis­chen bei­den Domä­nen hin- und her zu transformieren. 

Primäre und sekundäre Adapter

Zu einem Port gibt es einen oder mehrere Adapter. Aktive Ports enthal­ten primäre Adapter, das sind die Con­troller der Anwen­dung. Über sie gelan­gen Anweisun­gen und Dat­en von außen zum Anwen­dungskern, um dort die Geschäft­slogik auszuführen und Antworten zu erhalten.

Die sekundären Adapter der pas­siv­en Ports wer­den vom Anwen­dungskern genutzt, um externe Sys­teme einzu­binden. Beispiel­sweise andere Microser­vices, Mes­sag­ing- oder Stor­agesys­teme, wie die fol­gen­den Grafik ver­an­schaulicht. Sekundäradapter imple­men­tieren immer genau den Teil der exter­nen Schnittstelle, der vom Anwen­dungskern benötigt wird – auch hier wieder ISP.

Abb.: Primäre und sekundäre Adapter

Zu beacht­en ist hier­bei die Rich­tung der Abhängigkeit­en: Primäre Adapter rufen den Anwen­dungskern, dieser ken­nt jedoch die primären Adapter nicht. Sekundäre Adapter wer­den vom Anwen­dungskern benutzt, ken­nen diesen jedoch nicht. Die Aufrufhier­ar­chie im Hexa­gon geht also von links nach rechts, wie fol­gen­des Bild veranschaulicht:

Abb.: Die Aufrufhier­ar­chie im Hexagon

Application

Der Appli­ca­tion-Ring im Hexa­gon markiert die Verbindungss­chicht zwis­chen der Infra­struk­tur und dem Domainkern. Hier resi­diert die App­lika­tion­slogik. Das ist der Teil der Logik, der nicht die Geschäft­sregeln bein­hal­tet, wohl aber die Use Cas­es anstößt und koor­diniert. Außer­dem sind hier die tech­nis­chen Belange der App­lika­tion abge­bildet, beispiel­sweise Secu­ri­ty, Log­ging, Daten­bank­transak­tio­nen oder Ses­sion­han­dling. Bei der vielz­i­tierten Tren­nung von Tech­nik und Fach­lichkeit kommt dem Appli­ca­tion­lay­er eine entschei­dende Bedeu­tung zu. Es sind in erster Lin­ie die Appli­ca­tion Ser­vices, die seine Sub­stanz bilden.

Application Service

Appli­ca­tion Ser­vices definieren die Ein­stiegspunk­te für Use Cas­es und koor­dinieren deren Abläufe. 

Abb.: Der Appli­ca­tion Lay­er mit seinen Appli­ca­tion Services

Ein Appli­ca­tion Ser­vice wird von einem Primäradapter, beispiel­sweise einem REST-Adapter oder Mes­sage-Con­sumer, aufgerufen. Der Appli­ca­tion Ser­vice gibt den Aufruf an den Domain Core weit­er und stößt dadurch den zuge­höri­gen Use Case an. Zusam­men­hän­gende Use Cas­es wer­den vom Appli­ca­tion Ser­vice koor­diniert, einzelne Ergeb­nisse wer­den zu Antworten aggregiert und erforder­liche Events, z.B. Mail­ver­sand oder Noti­fi­ca­tions für andere Ser­vices, wer­den angetrig­gert. Wo nötig, kom­mu­niziert der Appli­ca­tion Ser­vice mit den Sekundäradaptern der pas­siv­en Ports, um beispiel­sweise Enti­tys aus einem Daten­bankrepos­i­to­ry oder von ein­er ent­fer­n­ten REST-API zu laden, die für den Ablauf des Use Cas­es benötigt wer­den. Wie bere­its erwäh­nt, enthält der Appli­ca­tion Ser­vice selb­st keine Geschäftslogik.

Domain Core

Der Domain Core ist der fach­liche Kern der App­lika­tion. Er enthält die fach­liche Imple­men­tierung der Use Cas­es mit der auszuführen­den Geschäft­slogik. Außer­dem die Imple­men­tierung der Fachk­lassen, das soge­nan­nte Domain Mod­el.

Abb. Der Domain Core im Hexa­gon mit Domain Mod­el und Domain Ser­cvices

Domain Model

Das Domain Mod­el bein­hal­tet im Wesentlichen die in dem fach­lichen Kon­text – Bound­ed Con­text im DDD-Jar­gon – iden­ti­fizierten Entitäten. Diese bilden den eigentlichen Kern der App­lika­tion. Hier ist das Wis­sen der Fach­domäne enthal­ten, soweit es zur Durch­führung der abzu­bilden­den Use Cas­es erforder­lich ist. Im Domain Dri­ven Design ist das Domain Mod­el das Herzstück der Soft­ware. Appli­ca­tion- und Infra­struc­ture­lay­er wer­den um den Domainkern herum ange­ord­net und sind vom diesem abhängig, nicht umgekehrt. Die Darstel­lung als konzen­trisches Hexa­gon ist dazu geeignet, dieses wichtige Grund­prinzip eingängig zu visu­al­isieren. Die Geschäft­slogik ist im Rich Domain Mod­el in den Meth­o­d­en der Fachk­lassen imple­men­tiert. Teile der Logik, die kein­er Fachk­lasse direkt zuge­ord­net wer­den kön­nen, wer­den in den Domain Ser­vices implementiert.

Domain Services

Die Domain Ser­vices enthal­ten über­greifende Geschäft­slogik, die nicht eins-zu-eins auf Fachk­lassen abge­bildet wer­den kann. Domain Ser­vices wer­den vom Appli­ca­tion-Lay­er, genauer von den dor­ti­gen Appli­ca­tion Ser­vices aufgerufen. Ein Domain Ser­vice darf direk­ten Zugriff auf die Infra­struc­ture haben und deren pas­sive Adapter benutzen. Hier­bei ist allerd­ings abzuwä­gen, ob der Aufruf bess­er in den Appli­ca­tion Lay­er zu ver­lagern wäre. 

Im ersten Fall kann der Domain Lay­er ein IoC-Inter­face bere­it­stellen, um den Adapter von der Domain zu entkoppeln. 

Im zweit­en Fall würde der Appli­ca­tion Ser­vice mit der Infra­struc­ture kom­mu­nizieren und das Ergeb­nis des Aufrufs in adäquater Form an die Domain übergeben. Diese Vari­ante sollte bevorzugt einge­set­zt wer­den, es sei denn, prak­tis­che Gründe sprechen für den anderen Weg.

Fern­er ist darauf zu acht­en, dass keine Infra­struc­ture-Depen­den­cys in die Domain hineinge­zo­gen wer­den, beispiel­sweise durch Ref­eren­zen auf DTOs als Aufruf­pa­ra­me­ter oder Rück­gabe­w­erte eines Adapters. Diese müssen vor der Über­gabe zunächst inner­halb der Adapter-Ebene auf Domain-Enti­tys gemappt wer­den. Die Map­pin­glogik fungiert dabei als Anti-Cor­rup­tion-Lay­er (ACL) nach DDD, der dafür sorgt, dass das Mod­el des Domain Cores nicht durch fremde Mod­els extern­er APIs „ver­schmutzt“ wird.

Abhängigkeiten und Aufrufhierarchien

Das Hexa­gon beste­ht aus zwei logis­chen Hälften, der linken aktiv­en Seite und der pas­siv­en recht­en. In der Abbil­dung ist zu erken­nen, dass Aufrufe nur in eine Rich­tung gehen, und zwar von links nach rechts. Niemals würde ein pas­siv­er Adapter einen Appli­ca­tion Ser­vice aufrufen, oder dieser einen aktiv­en Adapter. Solche Calls sind strikt verboten. 

Die Abhängigkeit­en der Schicht­en gehen immer von außen nach innen. Allerd­ings gibt es keine Depen­den­cy von dem pas­siv­en Teil des Infra­struc­ture Lay­ers zum Appli­ca­tion Layer. 

Abb.: Aufrufhier­ar­chien und Abhängigkeit­en zwis­chen den Grund­bausteinen des Hexagons

Somit lässt sich zusammenfassen:

  • Jed­er Baustein kann eine Depen­den­cy zum Domain Mod­el haben. 
  • Die Benutzung der Geschäft­slogik bleibt jedoch den Appli­ca­tion Ser­vices vorbehalten.
  • Appli­ca­tion Ser­vices kön­nen sowohl die Sekundäradapter der pas­siv­en Ports als auch die Domain Ser­vices benutzen.
  • Domain Ser­vices kön­nen Sekundäradapter benutzen, diese kön­nen durch IoC entkop­pelt werden.
  • Das Domain Mod­el hat kein­er­lei Abhängigkeit­en zu anderen Bausteinen.

Ausblick

Im näch­sten Teil tauchen wir tiefer in die einzel­nen Ringe hinein und definieren deren Inhalte im Detail. Weit­er­hin müssen wir eini­gen Fra­gen nachge­hen, die bei der Umset­zung eines Architek­tur­musters immer wieder gestellt wer­den. Zum Beispiel, was ist Busi­ness­logik, was ist Anwen­dungslogik, und worin unter­schei­den sie sich? In welchem Teil des Hexa­gons wer­den Domain­ob­jek­te erzeugt, die sich aus Dat­en von mehreren Drittsys­tem zusam­menset­zen? Wo wer­den Quer­schnitts­the­men wie Daten­va­li­dierung, Excep­tion­han­dling, Log­ging usw. implementiert? 

Es bleibt also span­nend. Bis dahin, hap­py engineering 🙂

„Pat­terns are use­ful because it gives soft­ware pro­fes­sion­als a com­mon vocab­u­lary with which to communicate. “

Jef­frey Palermo