Zwiebeln statt Layer

Die Zwiebelarchitektur und ihre Vorzüge

Daniel Marbach

© iStockphoto.com / ivanmateev

Zwiebeln schneiden brennt in den Augen, nicht aber in der Softwarearchitektur. Ganz im Gegenteil. Die Zwiebelarchitektur (Onion Architecture), eingeführt durch Jeffrey Palermo, stellt die weitbekannte Layer-Architektur (Layered Architecture) auf den Kopf. Lernen Sie die Zwiebelarchitektur und ihre Vorteile in diesem Artikel an einem praktischen Beispiel kennen. Kombiniert mit der Strukturierung Ihres Codes nach Features wird Ihre Architektur nach diesem Muster einfacher zu verstehen, zu ändern und zu erweitern sein.

Für lange Zeit war die Standardantwort auf die Frage, wie Komponenten und Klassen in der Softwarearchitektur organisiert werden sollen: mit Layer. Bevor wir genauer betrachten, welche Vorzüge Layer mit sich bringen und wie sie in der Softwarearchitektur integriert werden, ist es zunächst erforderlich, mit Missverständnissen im Bereich Layer und Tier aufzuräumen.

Layer vs. Tier

Wenn wir über Layer sprechen, meinen wir die logische Trennung oder Aufteilung von Komponenten und ihre Funktionalität, nicht aber den physikalischen Aufenthaltsort einer Komponente auf verschiedenen Servern. Der Begriff Tier hingegen meint die physikalische Verteilung einer Komponente und Funktionalität auf separaten Servern, inklusive der Netzwerktopologie. Mit Tier ist das physikalische Verteilungsmuster wie z. B. „2 Tier“, „3 Tier“ und „N Tier“ gemeint. Allerdings haben Layer und Tiers oftmals gleiche oder ähnliche Namen. Da wir in diesem Artikel Letztere behandeln werden, ist es notwendig, Layer zunächst genauer einzuordnen. Was also ist ein Layer – außer einer logischen Auftrennung – und wann wurden Layer eingeführt?

Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad und Michael Stal analysierten 1996 verschiedene Softwaresysteme. Sie stellten sich die Frage, welche Patterns Softwaresysteme erfolgreich machen und es ermöglichen, die Systeme weiterzuentwickeln, ohne dass dabei so genannte „Big Ball of Mud“-Systeme entstehen. Ihr geballtes Wissen ist im Buch „Pattern-Oriented Software Architecture Volume 1 – A System of Patterns“ publiziert (Buschmann, Frank; Meunier, Regine; Rohnert, Hans; Sommerlad, Peter; Stal, Michael: „Pattern-Oriented Software Architecture Volume 1 – A System of Patterns“, Wiley Series).

Eine wichtige Kernaussage des Buchs ist, dass große Softwaresysteme in kleinere Einheiten zerlegt werden müssen, um strukturelle Integrität zu bewahren. Das so genannte Layer-Pattern soll dabei helfen, Applikationen zu strukturieren, indem sie in Gruppen von Subtasks – die wiederum Subtasks bis hin zu einer bestimmten Stufe an Granularität enthalten – zerlegt werden. Die ursprüngliche Idee kam vom OSI-7-Layer-Modell, definiert von der Internationalen Organisation für Normung. All das bildet die Grundlage des originalen N-Layer-Modells.

Das N-Layer-Modell

In der Hierarchie höher angesiedelt Layer (Layer N+1) nutzen ausschließlich Dienste der darunterliegenden Layer (Layer N). Es sind keine weiteren direkten oder indirekten Abhängigkeiten zwischen den Layern erlaubt. Dadurch schützt jeder Layer die darunterliegenden Layer vor direktem Zugriff. Auf diese Weise wird das Prinzip von Datenkapselung (Information Hiding) erfüllt. Alle Komponenten innerhalb eines individuellen Layers besitzen denselben Abstraktionsgrad. Dieser Ansatz wird als „striktes“ Layering bezeichnet. Dass so genannte flexible Layering ist weniger restriktiv in Bezug auf Beziehungen zwischen den Layern. Jeder Layer darf die Dienste aller darunterliegenden Layer verwenden. Dieser Ansatz bietet durch reduziertes Mapping der Daten zwischen den Layern in der Regel mehr Flexibilität und Performance. Allerdings auf Kosten einer reduzierten Wartbarkeit.

Um Layer zu definieren und die Komponenten einem bestimmten Layer zuweisen zu können, gilt es, Abstraktionskriterien zu definieren. Ein mögliches Abstraktionskriterium für die unteren Layer ist z. B. der Abstand von der Hardware und für die oberen Layer die konzeptionelle Komplexität. Mögliche Layer wären (von oben nach unten):

  • Vom Benutzer sichtbare Elemente
  • Spezifische Applikationsmodule
  • Gemeinsame Dienste
  • Betriebssystemschnittstellen
  • Betriebssystem
  • Hardware

Die im Buch „Pattern-Oriented Software Architecture Volume 1 – A System of Patterns“ ursprünglich definierten Layer sind in Abbildung 1 aufgeführt.

Abb. 1: Original-Layer aus „Pattern-Oriented Software Architecture“

Abb. 1: Original-Layer aus „Pattern-Oriented Software Architecture“

Andere Bücher oder Artikel benennen diese Layer zum Teil anders, wir bleiben aber bei der ursprünglichen Definition. Es gibt den Präsentations- oder Client-Layer, den Prozess- oder Service-Layer, den Domänen- oder Geschäftslogik-Layer und den Datenzugriffs- oder Infrastruktur-Layer. Manchmal ist auch ein weiterer Layer zu sehen, der auf der linken Seite alle Layer umspannt. Dieser Layer wird häufig als Querschnitts-Layer bezeichnet, der u. a. Aspekte wie Tracing und Logging behandelt. Die Vorteile dieses Ansatzes sind:

  • Layer-Wiederverwendung: Wenn ein Layer wohldefinierte Abstraktionen sowie wohldefinierte und dokumentierte Schnittstellen gegen außen anbietet, kann er in verschiedenen Kontexten wiederverwendet werden. Hierfür scheint der Datenzugriffs-Layer von Natur aus sehr passend zu sein.
  • Unterstützung von Standards: Klar definierte und akzeptierte Abstraktionslevels ermöglichen das Entwickeln von standardisierten Komponenten und Schnittstellen. So finden wir z. B. einige Tools und Hilfsklassen, um automatisch Daten zwischen den verschiedenen Layer zu übersetzen.
  • Lokale Abhängigkeiten: Standardisierte Schnittstellen zwischen Layern halten die Effekte von Codeänderungen in der Regel innerhalb des Layers, der geändert wird.
  • Layer-Austausch: Individuelle Layer-Implementationen können durch einen semantisch äquivalenten Layer theoretisch ohne großen Aufwand ausgetauscht werden.

Auf den ersten Blick scheint das Layer-Modell sehr verständlich und einfach in der Umsetzung. Allerdings werden Layer von Entwicklern oft sehr strikt umgesetzt. Mit welcher Situation wir im Anschluss konfrontiert sind, zeigt Abbildung 2.

Abb. 2: Was wir schlussendlich bekommen

Abb. 2: Was wir schlussendlich bekommen

Anstatt das Beste aus den Vorteilen der Layer-Architektur zu erhalten, haben wir nun einige Layer, die von den darunterliegenden Layern direkt abhängig sind. Nehmen wir zum Beispiel die vorher definierte Layer-Struktur, so ist der Präsentations-Layer vom Applikations-Layer und dann vom Domänen-Layer und schlussendlich vom Datenzugriffs-Layer abhängig. Dies bedeutet, dass jeder Layer mit dem darunterliegenden Layer gekoppelt ist. Die darunterliegenden Layer sind wiederum jeweils an die Infrastruktur gekoppelt. Zwar braucht eine Applikation Kopplung, um überhaupt eine sinnvolle Aufgabe erfüllen zu können, jedoch kreiert dieser Ansatz unnötige Kopplungen.

Das größte Problem ist die Kopplung der Benutzerschnittstelle und Geschäftslogik zum Datenzugriffs-Layer. Halt! Die Benutzerschnittstelle ist gekoppelt zum Datenzugriffs-Layer? Ja, genau. Transitive Abhängigkeiten sind ebenfalls Abhängigkeiten. Die Benutzerschnittstelle kann nicht funktionieren, wenn die Geschäftslogik nicht vorhanden ist. Die Geschäftslogik kann nicht funktionieren, wenn der Datenzugriff nicht vorhanden ist. Wir ignorieren hierbei die Infrastruktur, da diese von System zu System stark variiert. Wenn wir die obige Architektur in der Retrospektive analysieren, stellen wir fest, dass der Datenzugriffs-Layer das Fundament der gesamten Applikationsstruktur bildet. Er wird zum kritischen Layer. Alle Änderungen auf dem Datenzugriffs- aber auch auf dem Infrastruktur-Layer betreffen alle darüberliegenden Layer. Das bedeutet, dass solche Änderungen von unten nach oben durch alle Schichten der Applikation durchstoßen.

Dieses Architektur-Pattern stützt sich fast vollständig auf die Infrastruktur. Der Code, der die eigentliche Geschäftslogik abbildet, füllt die Lücken zwischen den Infrastruktur-Bits. Wenn sich ein Prozess- oder Domänen-Layer an die Infrastruktur koppelt, ist dies eine unnötige Kopplung, und es kann zu Komplikationen beim Testen des Layers kommen. Gerade dieser Layer sollte fast nichts über Infrastruktur wissen. Die Infrastruktur sollte die Geschäftslogik unterstützen und nicht umgekehrt. Ebenso sollten Entwicklungen ausgehend von der Geschäftslogik starten und nicht ausgehend vom Datenzugriffscode. Zudem sollte die nötige Infrastrukturverdrahtung ein Implementationsdetail sein.

Layer.Factory-Beispiel

Um die Layer-Architektur genauer zu analysieren, nutzen wird das Layer.Factory-Beispiel auf GitHub. Das Layer.Factory-Beispiel ist ein einfaches Domain-driven-Design-Beispiel, das die Layer-Architektur umsetzt. Die Idee ist, dass das zugrunde liegende Domänenmodell eine Fabrik modelliert, die Layer produziert. Um Layer produzieren zu können, muss zunächst eine Fabrik erzeugt werden. Um das Beispiel und die Struktur zu visualisieren, nutzen wir Structure101 Studio, ein kommerzielles Tool, das hilft, große Codebasen zu analysieren und zu reorganisieren. Das Tool unterstützt mehrere Programmiersprachen. Wenn wir die Beispielapplikation damit analysieren, dann sehen wir, dass die Applikation wohlstrukturiert ist. Sie hat keine zyklischen Abhängigkeiten zwischen Namensräumen oder Klassen und besitzt keine überfüllten Namensräume oder Klassen. Aus struktureller Sicht ist unsere Applikation in einem sehr guten Zustand. Die Abhängigkeiten gehen von oben nach unten von Layer zu Layer (Abb. 3). Es handelt sich hier also um ein striktes Layering.

Abb. 3: Überblick der Layer.Factory-Layer-Abhängigkeiten

Abb. 3: Überblick der Layer.Factory-Layer-Abhängigkeiten

Um zu sehen, wie die Applikation intern aufgebaut ist, müssen wir noch tiefer in die Namensräume eintauchen, wie in Abbildung 4 zu sehen ist.

Abb. 4: Abhängigkeiten vom Presenter zum Prozess-Layer

Abb. 4: Abhängigkeiten vom Presenter zum Prozess-Layer

Der Einstiegspunkt in den Präsentations-Layer ist der LayerProductionPresenter. Dieser nutzt den ILayerProductionApplicationService, um eine Fabrik zu eröffnen und damit Layer zu erstellen. Weil die Applikation dem strikten Layering folgt, muss der Prozess-Layer die Domänenobjekte in Datentransferobjekte, die im Prozess-Layer definiert sind, übersetzen (FactoryInfo und LayerInfo). Um die Informationen auf den Views darzustellen, darf der Präsentations-Layer ausschließlich diese Datentransferobjekte verwenden. Die Daten, die im Domänenmodell abgebildet sind, müssen von Layer zu Layer übersetzt werden.

Tauchen wir weiter in den Domänen-Layer ein, wird das Problem noch sichtbarer. Der LayerProductionApplicationService nutzt einige Domänenservices. Diese Domänenservices implementieren die Kerngeschäftslogik der Applikation und stellen direkt die Aggregate, Entities und Value Objects des Domänenmodells zur Verfügung (z. B. Factory, FactoryId, FactoryName, Layer, LayerQuantity).

Abb. 5: Abhängigkeiten von den Applikationsservices zu den Domänenservices

Abb. 5: Abhängigkeiten von den Applikationsservices zu den Domänenservices

Wie wir in Abbildung 5 sehen können, werden Daten von oben nach unten und von unten nach oben übersetzt. Eingabeinformationen fließen nach unten vom Präsentations- zum Prozess-Layer und müssen übersetzt werden vom Präsentations-Layer- zu Prozess-Layer-Datentransferobjekten und schlussendlich zu Domänenobjekten. Ausgabeinformationen vom Domänen- zum Prozess-Layer müssen von Domänenobjekten zu Prozess- und schlussendlich zu Präsentationsdatentransferobjekten übersetzt werden. Solange lediglich Daten transferiert werden, ist der Übersetzungsprozess zwar umständlich, aber machbar. Sobald allerdings die Präsentationsschicht die Geschäftsregeln der Kerndomäne wiederverwenden will, nehmen die Nachteile dieses Ansatzes überhand. Doch wie können wir die Nachteile der Layer-Architektur umgehen? Mit Zwiebeln!

Zwiebelarchitektur

Das Prinzip der Zwiebelarchitektur ist sehr einfach. Wir schieben einfach alle Infrastruktur- und Datenzugriffsbelange in das Äußere der Applikation und lassen sie nicht im Zentrum. Jeffrey Palermo erwähnte diesen Ansatz, genannt Zwiebelarchitektur (Onion Architecture), das erste Mal auf seinem Blog im Jahre 2008. Die Bezeichnung soll es erleichtern, sich das dahinterliegende Architekturmuster besser merken zu können. Der Ansatz ist allerdings nicht neu. Ähnliche Ansätze wurden bereits in Ports and Adapters (Cockburn), Screaming Architecture (Robert C. Martin), Data Context Interaction (DCI, James Coplien und Trygve Reenskaug) und BCE (A Use Case Driven Approach von Ivar Jacobson) vorgestellt.

Das Hauptversprechen der Zwiebelarchitektur ist, dass sie die Kopplung besser in den Griff bekommt. Die fundamentale Regel besagt, dass Code von Ringen abhängig sein darf, die nahe am Zentrum liegen, nicht aber von Ringen, die weiter außerhalb liegen (Abb. 10). In anderen Worten: Alle Kopplung geht in die Richtung des Zentrums. Der Architekturansatz bevorzugt die objektorientierte Programmierung und stellt Objekte über alles andere.

Darüber hinaus basiert die Zwiebelarchitektur auf den Prinzipien von Domain-driven Design (siehe: Vernon, Vaughn: „Implementing Domain-Driven Design“, Addison-Wesley Professional, Chapter 4 Hexagonal or Ports and Adapters). Allerdings ist es nur dann sinnvoll, die Prinzipien des Domain-driven Designs anzuwenden, wenn die Applikation eine gewisse Größe und Komplexität aufweist. Bevor man mit der Zwiebelarchitektur startet, sollte man entsprechende Überlegungen treffen und nicht einfach blind darauf losstürzen. Domain-driven Design hat, wie fast jeder Architektur-/Designansatz, einen Preis. Sehen wir uns genauer an, was die Zwiebelarchitektur in Kombination mit Domain-driven Design ergibt (Abb. 6).

Abb. 6: Zwiebelarchitektur kombiniert mit Domain-driven Design

Abb. 6: Zwiebelarchitektur kombiniert mit Domain-driven Design

Im Innern sehen wir das Domänenmodell, das den Zustand und das Verhalten modelliert, das den Geschäftsprozess der jeweiligen Applikationsdomäne abbildet (alles Wichtige für die Geschäftsdomäne wie Domänenmodell, Validierungsregeln, Geschäftsprozesse etc.). Die Anzahl der Ringe im Applikationskern kann stark variieren, doch das Domänenmodell ist immer der innerste Ring. Weil alle Kopplung nach innen geht, ist das Domänenmodell nur mit sich selbst gekoppelt.

Der erste Ring um das Domänenmodell ist typischerweise der Ring, in dem wir die Schnittstellen finden, die es erlauben, Objekte zu speichern oder zu laden (z. B. Repository-Schnittstellen). Das Verhalten selbst (also die Implementation der Schnittstelle) ist nicht im Applikationskern, weil dabei typischerweise ein Speichermedium involviert ist (was wiederum ein Infrastrukturaspekt ist). Nur die Schnittstellen sind im Kern.

In den äußeren Ringen finden wir die Benutzerschnittstelle, die Infrastruktur und die Tests. Die äußeren Ringe sind reserviert für Dinge, die sich oft ändern. Mit diesem Ansatz sichern wir ab, dass der Applikationskern nicht geändert werden muss, wenn sich die Benutzerschnittstelle, der Datenzugriff, die Web Services, die Nachrichteninfrastruktur oder die I/O-Technik ändert.

Die Zwiebelarchitektur basiert auf dem so genannten Dependency-Inversion-Prinzip. Der Applikationskern braucht Implementationen der Kernschnittstellen. Weil die Implementationen in den äußeren Ringen abgelegt sind, braucht es einen Mechanismus, der die Implementationen zur Laufzeit an die Schnittstellen bindet und dem Kern übergibt. Werkzeuge wie Guice, Ninject etc. sind sehr nützlich für diesen Architekturansatz, aber nicht zwingend notwendig.

Die Applikation ist um ein unabhängiges Objektmodell konstruiert. Der komplette Applikationskern ist unabhängig, weil er keine externen Bibliotheken referenziert und somit keinen technologiespezifischen Code beinhaltet. Die inneren Ringe definieren die Schnittstellen. Diese Schnittstellen müssen den Geschäftsfall abbilden, nicht aber technische Aspekte. Dies bedeutet, dass die Form der Schnittstelle direkt auf den Geschäftsfall passt und somit konsumentengetrieben ist.

Der Kern übernimmt die Verantwortung über die Schnittstellen. Die Klassen und Komponenten in den äußeren Ringen implementieren die Schnittstellen, d. h. aller technologiespezifische Code ist in den äußeren Ringen. Der äußerste Ring kann Referenzen zu externen Bibliotheken haben. So kann ermöglicht werden, die Komplexität der Infrastruktur (welche nichts mit der Geschäftslogik zu tun hat) so weit wie möglich an den Rand der Applikation zu verschieben, wodurch die Kopplung immer mehr in Richtung Zentrum geht. Dieser Ansatz macht die Applikation unabhängig von verschiedenen Infrastruktur- und Querschnittsbelangen:

  • Datenbank: Da die Geschäftsregeln unabhängig von der Datenbank sind, kann das Speichermedium ausgetauscht werden.
  • Benutzerschnittstelle: Sie kann Änderungen vornehmen, ohne den Rest des Systems zu beeinflussen.
  • Frameworks: Die Architektur ist nicht abhängig von der Existenz einer bestimmten Bibliothek oder eines Frameworks. Die Frameworks können daher als Tools betrachtet werden, und das System muss nicht in ihre Einschränkungen gepresst werden.

Dies führt uns direkt zum ultimativen Vorteil dieses Ansatzes. Der Applikationskern ist zu 100 Prozent überprüfbar und unabhängig (siehe: Growing Object Oriented Software Guided by Tests, Designing for Maintainability Page 47-49).

Onion.Factory-Beispiel

Um die Zwiebelarchitektur zu veranschaulichen, analysieren wir das Onion.Factory-Beispiel auf GitHub. Dieses ist ein einfaches Domain-driven-Design-Beispiel, das die Zwiebelarchitektur umsetzt. Die Idee ist, dass das zugrunde liegende Domänenmodell eine Fabrik modelliert, die Zwiebeln produziert. Um Zwiebeln produzieren zu können, muss zuerst eine Fabrik erzeugt werden. Um das Beispiel und die Struktur zu visualisieren, nutzen wir wieder Structure101 Studio. Wenn wir die Beispielapplikation damit analysieren, dann sehen wir, dass die Applikation wohlstrukturiert ist. Sie hat keine zyklischen Abhängigkeiten zwischen Namensräumen oder Klassen und besitzt keine überfüllten Namensräume oder Klassen.

Alle Klassen, die Abhängigkeiten zu konkreten Dingen, wie Datenbank, Dateisystem, Views und mehr besitzen, sind in den äußeren Ringen. Alle Abhängigkeiten fließen in Richtung des Kerns, wogegen keine Abhängigkeit vom Kern in die äußeren Ringe zeigt (Abb. 7).

Abb. 7: Abhängigkeiten von äußeren Ringen zum Kern

Abb. 7: Abhängigkeiten von äußeren Ringen zum Kern

Um die Applikationsstruktur selbst besser verstehen zu können, müssen wir in den Kern abtauchen. Abbildung 8 zeigt den Kern isoliert ohne die äußeren Ringe.

Abb. 8: Applikationskern isoliert betrachtet

Abb. 8: Applikationskern isoliert betrachtet

Der Kern selbst besteht nur aus purem Domain-driven Design und enthält nur das, was für die Geschäftsvorfälle relevant ist: die Businessregeln. Wenn wir uns an die Layer-Architektur im Layer.Factory-Beispiel zurückerinnern, wissen wir, dass wir die Daten von Datentransferobjekt zu Datentransferobjekt über die Layer übersetzen mussten. Doch wie sieht der Presenter in der Zwiebelarchitektur aus (Abb. 9)?

Abb. 9: Beziehungen Presenter zum Kern

Abb. 9: Beziehungen Presenter zum Kern

Der OnionProductionPresenter nutzt IOnionProductionApplicationService. Dies ist genau die gleiche Dependency, die wir vorher in der Layer-Architektur gesehen haben. Der wirkliche Unterschied wird im Bereich erkennbar, in dem der Presenter das Domänenmodell direkt nutzt. Der Presenter nutzt und zeigt die auf dem Domänenmodell verfügbaren Informationen direkt. Er verwendet auch die Businessregeln direkt wieder, die in den Aggregaten, Entitäten und Value-Objekten implementiert sind. Wir müssen das Rad also nicht neu erfinden.

Layer gegen Zwiebel

Wenn wir die Prinzipien der Zwiebelarchitektur auf die Layer-Architektur anwenden, dann müssen wir die Layer-Architektur auf den Kopf stellen (Abb. 10).

Abb. 10: Layer auf dem Kopf

Abb. 10: Layer auf dem Kopf

Der Hauptunterschied ist, dass der Datenzugriffs-Layer, der Präsentations-Layer und der Querschnitts-Layer sowie alles I/O-abhängige nun oben im Diagramm platziert wird und nicht unten. Ein weiterer Unterschied ist, dass die oberen Layer alle Layer unterhalb nutzen können und nicht nur die Layer direkt unter ihnen. Zumindest das könnte auch mit dem vereinfachten Layer-Ansatz (Relaxed Layering) erreicht werden (Abb. 11).

Abb. 11: Layer als Zwiebel

Abb. 11: Layer als Zwiebel

Setzen wir die traditionelle Layer-Architektur in konzentrische Kreise, so sehen wir auf den ersten Blick, dass die Applikation rund um den Datenzugriff und andere Infrastruktur gebaut ist. Weil die Applikation diese Kopplung besitzt, muss sie geändert werden, wenn sich der Datenzugriff, die Web Services etc. ändern. Der entscheidende Unterschied ist, wie mit der Infrastruktur umgegangen wird. Traditionelle Layer-Architektur koppelt direkt zur Infrastruktur.

Zwiebelarchitektur schiebt die Infrastruktur zur Seite und führt die notwendigen Abstraktionen ein. Die Abhängigkeit von Abstraktionen ist zwar ein altes Prinzip, die Zwiebelarchitektur allerdings stellt dieses in den Vordergrund.

Refaktorisierung vom Layer zur Zwiebel

Da wir nun die Unterschiede zwischen Layer und Zwiebeln kennen, können wir versuchen, das Layer.Factory-Beispiel auf die Zwiebelarchitektur zu übertragen. Zunächst müssen wir versuchen, so viele Namensräume wie möglich intakt zu halten. Sie werden im Beispiel genutzt, um Layer nach folgenden Schritten zu simulieren:

  • führe einen Core-Namensraum ein
  • schiebe den Process- und den Domain-Namensraum in den Core
  •  schiebe FactoryRepository und LayerRepository in den Data-Namensraum.

Wie sich der Code strukturiert, zeigt Abbildung 12.

Abb. 12: Von Layer zur Zwiebel

Abb. 12: Von Layer zur Zwiebel

Wir können bereits in diesem einfachen Beispiel erkennen, dass das Refactoring der Layer-Architektur hin zur Zwiebelarchitektur allein nicht ausreichend ist, um eine wahre Zwiebelarchitektur zu erreichen. Wir müssen Designänderungen einführen. Der neue Core hat noch immer ausgehende Abhängigkeiten. Um sich von diesen Abhängigkeiten zu lösen, müsste eine einfache Abstraktion als Schnittstelle definiert aus Sicht des Cores eingeführt werden. Diese Schnittstelle wiederum müsste in den äußeren Ringen implementiert werden (im Beispiel oben wäre dies die SystemClock). Des Weiteren müssen die unnötigen Abstraktionen entfernt werden, die durch die Layer-Architektur eingeführt wurden (beispielsweise Datentransferobjekte).

Fazit

Fassen wir zusammen, was wir gelernt haben. So einfach, wie es zu Beginn auch klingen mag – folgt man dem strikten Layer-Ansatz, kann dies zu komplexen Abhängigkeitsgraphen führen. Nutzt man den Zwiebelarchitekturansatz, ist es notwendig, sich von Beginn an Gedanken über die Richtung der Abhängigkeiten zu machen. Kombiniert mit dem Einsatz der notwendigen Abstraktionen erreichen wir einen unabhängigen Kern, der vollständig überprüfbar ist. Auf diese Weise kann genau das weiterentwickelt, geschützt und instandgehalten werden, was für die Anwendung am meisten zählt: die Domänenlogik.

Geschrieben von
Daniel Marbach
Daniel Marbach
Daniel Marbach ist Softwarearchitekt bei bbv Software Services AG und MVP für Integration. Als passionierter Blogschreiber, Sprecher und Mitbegründer der .NET Usergroup Zentralschweiz ist er auf einer immerwährenden Reise mit Überzeugung und Engagement.
Kommentare

Hinterlasse einen Kommentar

1 Kommentar auf "Die Zwiebelarchitektur und ihre Vorzüge"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Harald Wellmann
Gast
Merkwürdig, dass dieser Artikel auf einem Java-Portal ausschließlich Beispiele in C# verwendet. Um zu beurteilen, ob und wie dieser Ansatz in einem Java EE Umfeld funktioniert und ob er irgendwelche Vorteile bringt, wäre ein JPA/EJB/JAX-RS/CDI/JSF-Beispiel hilfreich. Wie soll in Abb. 10 ein Prozess auf ein Domänenobjekt zugreifen, wenn der Prozess keine Datenzugriffsobjekte verwenden darf? Warum sollte umgekehrt ein Datenzugriffsobjekt von der Prozessschicht abhängen müssen? Um OrderDao.save(Order) zu implementieren, brauche ich nichts über OrderService.calculateShippingCosts(Order) zu wissen. Erst Abb. 11 lässt durchblicken, dass die Prozessschicht eben doch von Datenzugriffsinterfaces abhängt, aber nicht von deren Implementierung. Also geht es hier einfach um die… Read more »