Warum Businesslogik nichts von Datenbanken wissen sollte – Clean Architecture & hexagonale Architektur
In der modernen Softwarearchitektur geht es längst um mehr als die Organisation von Klassen und Paketen. Aufgrund neuer Technologien, wechselnder Anforderungen und verschiedener Schnittstellen ist eine Entkopplung zu externen Frameworks notwendig. In diesem Zusammenhang werden oft die beiden Architekturstile Clean Architecture und hexagonale Architektur genannt. Beide fokussieren sich auf die Trennung der Geschäftslogik von technischen Details. Dieser Artikel zeigt die Gemeinsamkeiten und Unterschiede beider Architekturstile auf und erklärt, wie diese in der Praxis vorteilhaft kombiniert werden können.
Das gemeinsame Ziel: unabhängige Geschäftslogik
Durch externe Abhängigkeiten der eigenen Anwendung entstehen oft folgende Probleme: Die Domänenlogik ist von einem konkreten Datenbank-Framework abhängig, Geschäftslogik ist in REST-Controllern zu finden, Tests benötigen laufende Infrastruktur und Wechsel der Datenbank oder anderer Technologien sind nur mit hohem Aufwand möglich.
Das Problem der Geschäftslogik in REST-Controllern ist im untenstehenden Code-Beispiel aufgezeigt. Die Nachteile daran sind, dass die Geschäftslogik so nicht durch andere Schnittstellen wie Batch-Prozesse wiederverwendbar ist, isolierte Tests ohne Berücksichtigung von Spring-Komponenten oder Datenbankzugriffen nicht möglich sind und es zu unbeabsichtigter Beeinflussung der Geschäftslogik bei Änderungen an der REST-API kommen kann.
@RestController
public class OrderController {
@Autowired private CustomerRepository customerRepository;
@Autowired private OrderRepository orderRepository;
@PostMapping("/orders")
public void createOrder(OrderRequest request){
CustomerEntity customer = customerRepository.findById(request.customerId());
if(customer.getBalance() < request.totalPrice()) {
throw new IllegalStateException("Nicht genügend Guthaben");
}
OrderEntity order = mapOrderRequestToOrderEntity(request);
orderRepository.save(order);
}
...
}
Sowohl Clean Architecture als auch hexagonale Architektur bieten Lösungsansätze für derartige Probleme. Diese fokussieren sich auf die Unabhängigkeit der fachlichen Logik einer Anwendung von den technischen Details. Dadurch wird erreicht, dass sich die Fachlichkeit nicht an die Technik anpassen muss, sondern die Technik an die Fachlichkeit. Die hexagonale Architektur organisiert die Kommunikation der Anwendung mit externen Abhängigkeiten, während die Clean Architecture die interne Struktur der Anwendung beschreibt. Im Folgenden werden die beiden Architekturstile erläutert und deren Unterschiede aufgezeigt.
Hexagonale Architektur: Ports und Adapter
Das Ziel der hexagonalen Architektur ist die Entkopplung des Anwendungskerns von technischen Details der Außenwelt. Das bedeutet, dass der zentrale Anwendungskern nur über definierte Schnittstellen angesprochen werden und nach außen kommunizieren kann. Dadurch ist der Anwendungskern losgelöst von der Laufzeitumgebung, den Datenbanken und anderen externen Abhängigkeiten.
Um die Kommunikation mit der Umgebung zu ermöglichen, werden Ports definiert. Inspiriert ist dieser Ansatz von Hardware-Anschlüssen und Ports in Betriebssystemen. Für die Kommunikation müssen Protokolle und elektronische sowie mechanische Spezifikationen eingehalten werden. In der Hexagonalen Architektur werden die Schnittstellen des Anwendungskerns als Ports bezeichnet, die beispielsweise in Form von APIs realisiert werden. Dabei wird zwischen Input-Ports (Was kann die Anwendung tun?) und Output-Ports (Was benötigt die Anwendung von ihrer Umgebung?) unterschieden.
Die Implementierung der Ports und damit die technische Umsetzung erfolgt durch Adapter. Diese sind unter anderem für die Konvertierung externer Datenformate in das vom Anwendungskern erwartete Modell zuständig. Beispiele für Adapter sind REST-Controller, Datenbank-Repositories, Messaging-Consumer und andere externe APIs. Es kann mehrere Adapter für einen Port geben. Beispielsweise für Datenbanken einen SQL-Adapter und einen Mock-Adapter für Testzwecke. Durch die Ports und Adapter ist der Anwendungskern unabhängig von äußeren Änderungen an externen Frameworks oder Technologien.
Der Anwendungskern wird in der hexagonalen Architektur als Sechseck dargestellt. Dabei stellen die Ports die Kanten dar, an welche die Adapter angefügt werden können. Die Anzahl der Ports und Adapter ist jedoch beliebig und nicht auf sechs Ports beschränkt. Das Sechseck wurde gewählt, um das klassische Bild der Schichtenarchitektur aufzulösen. Häufig hat eine Anwendung nur einen Port für die Datenspeicherung und einen für die Dateneingabe über beispielsweise eine grafische Benutzungsoberfläche. Durch die hexagonale Architektur ergibt sich für den in der Einleitung gezeigten Anwendungsfall einer neuen Bestellung die folgende Struktur:
Der Anwendungskern application stellt Funktionen über port.in bereit und benötigt dafür Implementierungen für die in port.out definierten Schnittstellen. Die Implementierungen der Interfaces in port.out sind in adapter.out zu finden. Für die definierten Repository-Interfaces wurden dort beispielsweise Adapter mithilfe der Technologie JPA bereitgestellt. Der REST-Controller in adapter.in nutzt die über port.in bereitgestellten Funktionen. Dadurch ist der Anwendungskern in application isoliert und die Kommunikation mit diesem über die Ports möglich.
Clean Architecture: Abhängigkeiten zeigen nach innen
Das Ziel der Clean Architecture ist die Zusammenführung der gemeinsamen Ideen bisheriger Architekturstile. Dazu gehören Separation of Concerns, Strukturierung in Schichten, Testbarkeit und die Entkopplung der Domänenlogik von Frameworks, Benutzungsoberflächen, Datenbanken sowie von anderen externen Abhängigkeiten.
Metaphorisch wird die Clean Architecture als ineinander liegende Kreise dargestellt. Die Kreise stellen die Bereiche der Software dar, wobei die Abhängigkeiten nur nach innen zeigen dürfen. Die inneren Bereiche der Software wissen nichts von den äußeren Bereichen. Die inneren Bereiche beinhalten entsprechend Geschäftsregeln, Domänenmodelle oder fachliche Entscheidungen, während die äußeren Bereiche Datenbanken, Webframeworks, Messaging oder Benutzungsoberflächen beinhalten. Dadurch werden bei Änderungen in den äußeren Bereichen, wie beispielsweise einer anderen Datenbank, Anpassungen an inneren Bereichen vermieden.
Auch hier sind die Kreise nur schematisch und es gibt keine feste Anzahl an Bereichen. Die Anzahl der Kreise sollte so gewählt werden, dass bestimmte Teile langfristig stabil gehalten werden können. Instabile Bereiche sollten entsprechend in äußere Bereiche verlagert werden, um die stabilen inneren Bereiche zu schützen. Bei einer kleinen Anwendung mit wenig Fachlogik ist keine hohe Anzahl an Bereichen nötig und würde die Anwendung nur unnötig kompliziert machen. Eine hohe Anzahl an Bereichen heißt nicht automatisch eine bessere Architektur.
Um die Abhängigkeitsregel, dass Abhängigkeiten nur nach innen zeigen dürfen, nicht zu verletzen, sollte das Dependency Inversion Principle angewendet werden. Wenn ein innerer Bereich Dienste eines äußeren Bereichs benötigt, sollte die benötigte Schnittstelle im inneren Bereich definiert und im äußeren Bereich implementiert werden. So kann ein Service im inneren Bereich ein Interface zur Speicherung von Daten aufrufen, welches dann durch ein konkretes Datenbank-Repository in einem äußeren Bereich implementiert wird. Um Daten über Grenzen hinweg zu reichen, sollten Domänenmodelle, einfache Datenstrukturen oder DTOs verwendet werden.
Meist sind die Bereiche derart aufgebaut, dass die Entitäten ganz innen liegen, da sich diese am seltensten ändern. Danach folgen Anwendungsfälle des Systems, welche den Datenfluss von und zu den Entitäten koordinieren und anwendungsspezifische Geschäftsregeln beinhalten. Um mit der Umgebung zu kommunizieren, muss der Bereich vor konkreten Frameworks die Controller bereitstellen, um die Daten zu konvertieren und die Anwendungsfälle über Interfaces aufzurufen. Durch die Clean Architecture ergibt sich für das in der Einleitung gezeigte Beispiel die folgende Struktur:
Den innersten Bereich repräsentiert domain, welcher die Domänenmodelle und zentrale Geschäftslogik beinhaltet. Der Datenfluss von und zur Domain wird durch den zweiten Bereich usecase gesteuert, der die Anwendungsfälle beinhaltet. Der Controller in presentation darf den CreateOrderService gemäß der Abhängigkeitsregel der Clean Architecture direkt aufrufen. Da der CreateOrderService zur Datenspeicherung allerdings nicht direkt die Repositories in infrastructure aufrufen darf, wurde hier das Dependency Inversion Principle angewandt. Dadurch kann der Service die Interfaces in usecase aufrufen. Die Repositories in infrastructure müssen diese entsprechend implementieren.
Der wichtigste Unterschied: Perspektive und Fokus
Die beiden Ansätze haben ähnliche Ziele wie Wartbarkeit, Testbarkeit und Erweiterbarkeit. Allerdings weichen diese in der Art der Zielerreichung und der daraus resultierenden Projektstruktur voneinander ab.
Die hexagonale Architektur fragt nach einem Weg zur Kommunikation der Anwendung mit der Außenwelt. Diese soll über Schnittstellen als Ein- und Ausgänge ermöglicht werden, um die Kernlogik zu isolieren und Technologien austauschen zu können. Die Architektur wird mit der Metapher des Hexagons mit Ports und Adaptern dargestellt und versucht so, das Bild der klassischen Schichtenarchitektur aufzulösen.
Die Clean Architecture fragt nach einem Weg, die Abhängigkeiten innerhalb der Anwendung zu organisieren. Dafür werden Bereiche in Form von Kreisen dargestellt, um die Abhängigkeiten klar zu trennen und externe Komponenten von der Geschäftslogik fernzuhalten. Die Architektur wird mit der Metapher der ineinander liegenden Kreise dargestellt, bei denen die Abhängigkeiten nur nach innen zeigen dürfen.
Warum werden beide oft kombiniert?
In der Praxis ergänzen sich die beiden Ansätze oft, da die hexagonale Architektur helfen kann, die Kernprinzipien der Clean Architecture zu erreichen. Clean Architecture hilft bei der Strukturierung des Anwendungskerns, während die hexagonale Architektur die Kommunikation mit der Umgebung genauer definiert. Durch die Kombination beider Architekturstile ergibt sich für das in der Einleitung gezeigte Beispiel die folgende Struktur:
Anhand dieser Struktur ist zu erkennen, dass die hexagonale Architektur weiterhin die Pakete adapter und port für die Kommunikation mit der Umgebung vorgibt. Die Clean Architecture strukturiert die Klassen innerhalb der Anwendung durch die Extraktion des inneren Bereichs domain. Die äußeren Bereiche infrastructure und presentation der Clean-Architecture-Struktur sind in adapter zu finden.
Wann lohnt sich der Einsatz?
Durch die Kernprinzipien sowohl der hexagonalen Architektur als auch der Clean Architecture wird die klare Trennung der Kernlogik erzwungen. Es werden die unabhängige Testbarkeit sowie hohe Flexibilität, Erweiterbarkeit und Austauschbarkeit ermöglicht. Allerdings ergibt sich aus dem höheren Abstraktionsniveau und der feineren Gliederung auch eine höhere Komplexität. Dadurch entsteht vermehrter Aufwand bei der Einarbeitung in die Software sowie bei der Erstellung von Interfaces und Klassen. Ob sich der erhöhte Zeitaufwand für die Gestaltung und Umsetzung der Architektur lohnt und ob die Architekturstile geeignet sind, muss für jede Anwendung individuell bewertet werden.
Beide Architekturstile eignen sich bei langlebigen Anwendungen mit komplexen Geschäftsregeln, bei denen häufig wechselnde Technologien möglich sind, mehrere Frontends oder Schnittstellen angebunden werden sollen oder hohe Testanforderungen gestellt werden. Weniger geeignet sind diese entsprechend bei einfachen CRUD-Anwendungen, kurzfristigen Prototypen oder Anwendungen ohne relevante Geschäftslogik.
Fazit
Die hexagonale Architektur und die Clean Architecture haben viele Überschneidungen, sind jedoch nicht identisch. Die beiden Architekturstile können sowohl gemeinsam als auch getrennt voneinander eingesetzt werden. So kann eine Anwendung beispielsweise hexagonal aufgebaut sein, aber der Anwendungskern dennoch mithilfe der Schichtenarchitektur gestaltet sein.
Clean Architecture und hexagonale Architektur verfolgen dasselbe Ziel, die Fachlichkeit von technischen Details zu entkoppeln. Dabei haben die beiden Architekturstile jedoch unterschiedliche Blickwinkel. Die hexagonale Architektur beschreibt die Grenzen zwischen Anwendung und Außenwelt, während Clean Architecture die Organisation innerhalb dieser Grenzen beschreibt. Die beiden Ansätze lassen sich kombinieren, indem Clean Architecture die Struktur und die hexagonale Architektur das Kommunikationsmodell vorgibt. Dadurch ergibt sich eine Architektur, in der Geschäftslogik unabhängig bleibt, Technologien austauschbar sind und Änderungen dort stattfinden können, wo sie nötig sind.
Entdecken Sie unser Seminarangebot zum Themenschwerpunkt Softwareentwicklung
ZUM SEMINARSHOP
Mehr erfahrenSenior Consultant
Kommentare