Knowledge

Softwarearchitektur: Layered, Hexagonal und Clean Architecture kurz verglichen

Softwarearchitektur beginnt nicht beim Framework.

Frameworks geben Struktur vor. Sie legen aber nicht fest, welche Logik wohin gehört, wie Datenbankoperationen aufgebaut sein müssen und welche Regeln im Projekt gelten.

Solange ein Projekt überschaubar ist, läuft vieles rund.

Controller, Service, Repository, Datenbank.
Das reicht für den Start oft aus.

Schwieriger wird es, wenn das System wächst.

Plötzlich verteilt sich Logik in Controllern, Services verantworten mehrere Themen und Datenbankzugriffe erfolgen wild an unterschiedlichen Stellen.

Es entsteht schwer wartbarer Code und technische Schuld.

Dann ist nicht das Framework das Problem.

Das Problem sind fehlende Architekturregeln.

1. Layered Architecture

Layered Architecture ist der klassische Schichtenaufbau.

Controller
  -> Service
     -> Repository
        -> Database

Beispiel Rechnung erstellen:

InvoiceController.create(request)
  -> invoiceService.createInvoice(customerId, items)

InvoiceService.createInvoice(customerId, items)
  customer = customerRepository.find(customerId)
  invoice = new Invoice(customer, items)
  invoiceRepository.save(invoice)
Punkt Bewertung
Passt gut für kleine Anwendungen, CRUD-Anwendungen, interne Tools, überschaubare Logik, kleine Teams
Pro einfach verständlich, schnell umgesetzt, vielen Entwicklern bekannt, für einfache Anwendungen oft ausreichend
Contra Services werden schnell zum Sammelpunkt, Logik landet im Controller, Datenbankzugriffe verteilen sich, Tests werden schwieriger

Wichtige Regeln

✅ Gut ❌ Kritisch
Controller enthalten keine oder nur minimale Logik, zum Beispiel Auth-Prüfung oder Request-Validierung. Controller entscheiden über mehrere Services per if/else und steuern dadurch den Ablauf.
Services enthalten den Anwendungsablauf und koordinieren die notwendigen Schritte. Services werden zum Sammelbecken für Berechtigungen, Berechnungen, Datenbankzugriffe, API-Aufrufe und Sonderfälle.
Datenbankzugriffe laufen klar gekapselt über Repositories oder vergleichbare Datenzugriffsklassen. SQL oder ORM-Zugriffe tauchen in Controllern, Services, Helpern oder Jobs verteilt auf.
Regeln sind an einer nachvollziehbaren Stelle gebündelt. Die gleiche Regel wird an mehreren Stellen ähnlich, aber nicht identisch umgesetzt.
Tests können Services oder Anwendungslogik ohne komplette Oberfläche prüfen. Tests funktionieren nur, wenn Webserver, Datenbank und weitere Infrastruktur vollständig laufen.

2. Hexagonal Architecture

Hexagonal Architecture wird auch Ports-and-Adapters genannt.

Die Grundidee:

Die Logik steht in der Mitte.
Framework, Datenbank und Schnittstellen liegen außen.

REST Adapter
CLI Adapter
Message Adapter
   -> Use Case
      -> Port
         -> Database Adapter
         -> External API Adapter

Beispiel Rechnung erstellen:

RestInvoiceAdapter
  -> CreateInvoiceUseCase
     -> CustomerPort
     -> InvoicePort
     -> NotificationPort

CreateInvoiceUseCase.execute(command)
  customer = customerPort.getById(command.customerId)
  invoice = Invoice.create(customer, command.items)

  invoicePort.save(invoice)
  notificationPort.sendInvoiceCreated(invoice)

Die technische Umsetzung sitzt außen.

SqlInvoiceAdapter implements InvoicePort
MailNotificationAdapter implements NotificationPort
RestInvoiceController calls CreateInvoiceUseCase
Punkt Bewertung
Passt gut für wachsende Anwendungen, externe Schnittstellen, Messaging, Dateiimporte, Dateiexporte, Legacy-Migration, hohe Testanforderung
Pro Use Cases enthalten den eigentlichen Ablauf, Datenbank, Schnittstellen oder Mailversand lassen sich besser austauschen, Tests werden einfacher, mehrere Eingänge können denselben Use Case verwenden
Contra mehr Struktur, mehr Schnittstellen, mehr Benennung, für kleine Anwendungen schnell zu viel

Wichtige Regeln

✅ Gut ❌ Kritisch
Use Cases beschreiben einen klaren Anwendungsfall. Use Cases werden zu allgemeinen Service-Klassen ohne klare Aufgabe.
Die innere Logik arbeitet gegen Ports/Schnittstellen. Die innere Logik kennt konkrete Datenbank-, HTTP- oder Mail-Implementierungen.
Adapter enthalten technische Details wie REST, SQL, Mail oder Messaging. Technische Details wandern in den Use Case.
Mehrere Eingänge können denselben Use Case verwenden. REST, CLI und Messaging enthalten jeweils eigene Logik.
Tests prüfen den Use Case mit Fakes oder Mocks. Tests brauchen echte Datenbank, echten Mailserver oder externe Systeme.

3. Clean Architecture

Clean Architecture geht noch einen Schritt weiter.

Die Abhängigkeiten zeigen konsequent nach innen.

Framework
  -> Interface Adapter
     -> Use Case
        -> Entity

Beispiel Rechnung erstellen:

CreateInvoiceInteractor.execute(input)
  invoice = InvoiceEntity.create(input.customerId, input.items)

  invoiceGateway.save(invoice)

  outputPresenter.present(invoice)

Im Zentrum stehen Regeln und Anwendungsfälle.

Framework, Datenbank, Web, JSON, HTML oder externe Systeme liegen außen.

Punkt Bewertung
Passt gut für langlebige Kernsysteme, komplexe Logik, mehrere Teams, hohe Testanforderung
Pro klare Abhängigkeitsrichtung, Logik ist klar von Framework, Datenbank und Oberfläche getrennt, Framework spielt eine kleinere Rolle, hohe Testbarkeit
Contra hoher Strukturaufwand, viele Begriffe, höhere Einstiegshürde, für einfache Anwendungen oft zu schwer

Wichtige Regeln

✅ Gut ❌ Kritisch
Abhängigkeiten zeigen nach innen. Innere Schichten kennen Framework, Datenbank oder Webdetails.
Use Cases beschreiben konkrete Anwendungsfälle. Use Cases werden zu technischen Service-Sammlungen.
Entities enthalten zentrale Regeln. Entities sind nur Datencontainer ohne Bedeutung für die Logik.
Adapter übersetzen zwischen Technik und Kern. Controller, Presenter oder Gateways enthalten Entscheidungen zum Ablauf.
Die Architektur wird nur dort eingesetzt, wo der Aufwand gerechtfertigt ist. Clean Architecture wird für einfache CRUD-Masken durchgezogen, obwohl kein Nutzen entsteht.

4. Vergleich

Kriterium Layered Hexagonal Clean
Einstieg einfach mittel anspruchsvoll
Strukturaufwand gering mittel hoch
Testbarkeit mittel hoch hoch
Gut für CRUD ja teilweise eher selten
Externe Systeme begrenzt gut gut
Legacy-Migration begrenzt sehr gut gut, aber aufwendiger
Agentic Coding Workflows möglich, aber klare Regeln nötig gut geeignet durch klare Use Cases, Ports und Adapter gut geeignet, aber nur bei sauber verstandener Struktur
Risiko Overengineering gering mittel hoch

Agentic Coding braucht klare Regeln, wie Code zu schreiben ist.

Das gilt unabhängig von der Architektur.

Je klarer die Architektur abgegrenzt ist, desto eher ergeben sich diese Regeln direkt aus der Projektstruktur.

Bei Layered Architecture müssen Grenzen oft zusätzlich beschrieben werden.

Bei Hexagonal oder Clean Architecture sind viele Grenzen bereits sichtbarer: Use Case, Port, Adapter, Entity oder äußere Schicht.

5. Entscheidungshilfe

Situation Empfehlung
Kleine Anwendung mit wenig Logik Layered Architecture
Klassische CRUD-Anwendung Layered Architecture
Wachsende Anwendung mit mehreren Schnittstellen Hexagonal Architecture
Legacy-System soll schrittweise modernisiert werden Hexagonal Architecture
Langlebiges Kernsystem mit komplexer Logik Clean Architecture oder strenge Hexagonal Architecture
Team kennt die Architektur nicht sicher lieber einfacher starten und Regeln sauber einhalten

Typische Fehler

Fehler Problem
Logik im Controller Controller werden schwer testbar und schwer wartbar
Services als Sammelbecken Zuständigkeiten verschwimmen
Datenbankzugriffe an mehreren Stellen Änderungen am Datenmodell werden teuer
Externe Systeme direkt im Service Tests und Austauschbarkeit werden schwieriger
Architektur nur als Ordnerstruktur Der Code sieht sauber aus, ist aber trotzdem gekoppelt
Tests nur mit kompletter Infrastruktur Fehler werden spät gefunden und Änderungen werden riskanter

Auf den Punkt

Layered Architecture ist einfach und oft ausreichend.

Hexagonal Architecture schützt die Logik besser vor Framework, Datenbank und Schnittstellen.

Clean Architecture trennt am konsequentesten, ist aber nicht für jedes Projekt notwendig.

Je größer das Risiko durch Änderungen, Tests und Wartung ist, desto klarer muss die Architektur sein.

Gute Architektur ist kein Selbstzweck.

Sie soll Änderungen einfacher machen, nicht komplizierter.