Hexagonale Architektur in Kotlin: Sauberkeit und Flexibilität des Codes

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

Hexagonale Architektur in Kotlin: Wenn Code frei atmet

\n\n

Stellen Sie sich vor, Sie bauen ein Haus. Sie können sofort das Fundament gießen, Wände hochziehen und Fenster einsetzen. Oder Sie können zuerst den Rahmen entwerfen, die Leitungen verlegen und sich erst dann um die Innenausstattung kümmern. In der Programmierwelt wird der zweite Ansatz als hexagonale Architektur (auch Ports & Adapters) bezeichnet. Und Kotlin mit seiner Prägnanz und seinem leistungsstarken Typsystem ist die ideale Sprache, um dieses Konzept in die Tat umzusetzen.

\n\n

Dieser Artikel ist kein langweiliges Lehrbuch. Es ist ein praktischer Leitfaden, der zeigt, wie die hexagonale Architektur in Kotlin Ihren Code in ein flexibles, testbares und von externen Diensten unabhängiges System verwandelt. Wir werden ein Beispiel von Grund auf durchgehen, echten Code schreiben und sehen, wie das in der Praxis funktioniert.

\n\n

Was ist hexagonale Architektur und warum brauchen Sie sie?

\n\n

Alistair Cockburn, der diesen Begriff geprägt hat, verglich die Anwendung mit einem Sechseck (Hexagon). Im Zentrum befindet sich Ihre Geschäftslogik. An den Rändern befinden sich Ports (Schnittstellen), über die sie mit der Außenwelt kommuniziert. Adapter sind Implementierungen dieser Schnittstellen für bestimmte Technologien: Datenbank, Web-Framework, Message Queue.

\n\n

Die Kernidee: Die Geschäftslogik weiß nichts darüber, wie Daten gespeichert werden oder welches Framework verwendet wird. Sie ruft einfach Methoden der Ports auf. Möchten Sie PostgreSQL durch MongoDB ersetzen? Schreiben Sie einfach einen neuen Adapter. Die Geschäftslogik wird den Austausch nicht einmal bemerken.

\n\n

In Kotlin ist dies besonders praktisch dank Schnittstellen mit Default-Implementierungen, sealed-Klassen und funktionalem Stil. Kommen wir zur Sache.

\n\n

Systementwurf: Beispiel eines Online-Shops

\n\n

Angenommen, wir schreiben einen Microservice zur Verwaltung von Bestellungen. Im Zentrum steht OrderService, der Bestellungen erstellt und die Verfügbarkeit von Artikeln prüft. Die Außenwelt besteht aus einem HTTP-Controller und einer Datenbank.

\n\n

So sieht die Projektstruktur aus:

\n\n
src/\n├── domain/          # Kern (Geschäftslogik)\n│   ├── model/\n│   ├── port/\n│   └── service/\n├── adapter/\n│   ├── input/       # Eingehende Adapter (Controller)\n│   └── output/      # Ausgehende Adapter (Repositories)\n└── Application.kt   # Einstiegspunkt\n
\n\n

Schritt 1. Kern: Modelle und Ports

\n\n

Beginnen wir mit dem Bestellmodell. In Kotlin wird dies eine data class sein:

\n\n
// domain/model/Order.kt\ndata class Order(\n    val id: String,\n    val customerId: String,\n    val items: List<OrderItem>,\n    val status: OrderStatus\n)\n\nenum class OrderStatus {\n    PENDING, CONFIRMED, SHIPPED, CANCELLED\n}\n\ndata class OrderItem(\n    val productId: String,\n    val quantity: Int\n)\n
\n\n

Jetzt definieren wir die Ports – Schnittstellen, über die der Kern mit der Außenwelt kommuniziert. Wir werden zwei Ports haben: einen zum Abrufen von Produktdaten und einen zum Speichern von Bestellungen.

\n\n
// domain/port/ProductRepository.kt\ninterface ProductRepository {\n    fun checkAvailability(productId: String, quantity: Int): Boolean\n}\n\n// domain/port/OrderRepository.kt\ninterface OrderRepository {\n    fun save(order: Order): Order\n    fun findById(id: String): Order?\n}\n
\n\n

Beachten Sie: Es gibt keine Erwähnung von Datenbanken, HTTP oder Frameworks. Reine Geschäftslogik.

\n\n

Schritt 2. Business-Service

\n\n

Jetzt schreiben wir den OrderService, der diese Ports verwendet. In Kotlin übergeben wir Abhängigkeiten über den Konstruktor – dies wird als Abhängigkeitsinjektion (DI) bezeichnet.

\n\n
// domain/service/OrderService.kt\nclass OrderService(\n    private val productRepository: ProductRepository,\n    private val orderRepository: OrderRepository\n) {\n    fun createOrder(customerId: String, items: List<OrderItem>): Order {\n        // Prüfen der Verfügbarkeit jedes Artikels\n        for (item in items) {\n            if (!productRepository.checkA                

Blogs

Buchempfehlungen