Hexagonale Architektur in Java: saubere Logik ohne Schmerz

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum Ihre Anwendung nicht ihre Datenbank ist

\n\n

Stellen Sie sich vor, Sie bauen ein Haus. Fundament, Wände, Dach – alles ist durchdacht. Aber nach einem Jahr entscheiden Sie sich, die Elektrik zu erneuern. Und dann stellt sich heraus, dass die Kabel im Beton eingelassen und die Steckdosen an die Armatur geschweißt sind. Um einen einzigen Schalter auszutauschen, müssen Sie das halbe Haus abreißen.

\n\n

So ungefähr sieht eine klassische Java-Anwendung aus, in der die Geschäftslogik eng mit dem Framework, der Datenbank oder dem REST-Controller verwoben ist. Sie möchten von PostgreSQL auf MongoDB umsteigen? Die Controller von Spring MVC auf JAX-RS umschreiben? Willkommen in der Hölle des Refactorings.

\n\n

Der Ausweg ist die hexagonale Architektur (auch bekannt als „Ports und Adapter“ oder „Clean Architecture“ in der Interpretation von Alistair Cockburn). Sie ist kein Allheilmittel, aber eine Möglichkeit, Ihre Anwendung flexibel, testbar und unabhängig von der Außenwelt zu machen.

\n\n

Heute analysieren wir die hexagonale Architektur in Java mit echten Codebeispielen, ohne unnötige Füllwörter und abstrakte Überlegungen. Los geht's.

\n\n

Was ist hexagonale Architektur (Ports und Adapter)?

\n\n

Das Prinzip ist einfach: Ihre Anwendung ist ein Kern (Core), der die Geschäftslogik enthält. Um den Kern herum befindet sich ein „Sechseck“ (tatsächlich eine beliebige Anzahl von Seiten), über das die Außenwelt mit dem Kern kommuniziert.

\n\n

Jede Seite hat ihren eigenen Port (ein Interface). Ein Adapter ist die Implementierung des Ports für eine bestimmte Technologie. Zum Beispiel:

\n
    \n
  • Port „UserRepository“ – Adapter „JpaUserRepository“ oder „MongoUserRepository“
  • \n
  • Port „SendNotification“ – Adapter „EmailSender“ oder „SmsSender“
  • \n
\n\n

Die Hauptregel: Der Kern weiß nichts über externe Bibliotheken, Datenbanken oder Web-Frameworks. Er kennt nur seine Ports (Interfaces). Dies bietet mehrere Vorteile:

\n
    \n
  • Unabhängigkeit von der Infrastruktur – Sie wechseln die Datenbank oder das Framework, ohne die Logik zu berühren.
  • \n
  • Testbarkeit – Die Geschäftslogik kann getestet werden, ohne den Spring-Kontext oder die Datenbank hochzufahren.
  • \n
  • Lesbarkeit – Der Code des Kerns erzählt die Geschichte der Domäne, nicht technische Details.
  • \n
\n\n

Projektstruktur: Wie man den Code organisiert

\n\n

Es gibt keine einzig richtige Paketstruktur, aber ich schlage eine bewährte Variante auf Basis von DDD (Domain-Driven Design) und den Prinzipien der hexagonalen Architektur vor:

\n\n
com.example.app\n├── domain\n│   ├── model          // Entitäten, Value Objects\n│   ├── port           // Port-Interfaces (inbound/outbound)\n│   └── service        // Geschäftsservices (Use Cases)\n├── adapter\n│   ├── inbound\n│   │   ├── rest       // REST-Controller (eingehende Adapter)\n│   │   └── messaging  // Kafka/RabbitMQ-Listener\n│   └── outbound\n│       ├── persistence // JPA-, JDBC-, MongoDB-Repositories\n│       └── client      // HTTP-Clients, externe APIs\n└── config             // Spring-Konfiguration (falls verwendet)\n
\n\n

Beachten Sie: domain importiert nichts aus adapter. Das ist entscheidend. Wenn Sie @Entity oder @RestController in domain sehen, ist die Architektur kaputt.

\n\n

Beispiel: Auftragsverwaltungssystem

\n\n

Lassen Sie uns ein einfaches System entwerfen: Ein Benutzer kann eine Bestellung aufgeben, und das System prüft die Verfügbarkeit des Artikels im Lager und sendet eine Benachrichtigung.

\n\n

Schritt 1. Domänenmodell (domain/model)

\n\n
// Order.java\npublic class Order {\n    private final OrderId id;\n    private final List<OrderItem> items;\n    private OrderStatus status;\n\n    public Order(List<OrderItem> items) {\n        this.id = new OrderId(UUID.randomUUID().toString());\n        this.items = Collections.unmodifiableList(items);\n        this.status = OrderStatus.PENDING;\n    }\n\n    public void confirm() {\n        if (status != OrderStatus.PENDING) {\n            throw new IllegalStateException("Only PENDING orders can be confirmed");\n        }\n        this.status = Ord                

Blogs

Buchempfehlungen