Loslegen
Javaneer
Zurück zur Stufe
Stufe 3·Hexagonale & Clean Architecture

Die Dependency Rule

Die eine Idee hinter hexagonaler, Onion- und Clean-Architektur: Quellcode-Abhängigkeiten zeigen nur nach innen, zur Domäne - damit der Kern nie von Datenbank oder Web-Schicht abhängt.

15 Min. LesezeitExperte

Deutsche Übersetzung in Arbeit

Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.

Auf dieser Seite

You've built up to this. Dependency Inversion (SOLID) said "depend on abstractions." Aggregates and repositories (DDD) put the domain at the center of your objects. Hexagonal architecture - and its cousins Onion and Clean

  • take that idea and make it the shape of the entire system. They share one non-negotiable law, and if you remember nothing else from this module, remember it: source-code dependencies point only inward, toward the domain.

The problem: the framework in the middle

In a typical layered app - and in ledger-legacy - the database sits at the conceptual center. Business logic imports JPA entities, calls the persistence API directly, and is littered with framework annotations. The domain depends on the infrastructure:

Controllers → Services → JPA Repositories → Database
   (everything depends on the layer below; the domain knows about the DB)

The consequence: you can't understand or test the business rules without the database, you can't swap Postgres without rewriting logic, and framework upgrades ripple through your core. The most valuable, stable code (the domain) is chained to the most volatile, replaceable code (the infrastructure). That's backwards.

The Dependency Rule

Clean/hexagonal architecture inverts this. Picture concentric circles with the domain at the center and infrastructure at the edge. The rule: a dependency may only point inward.

        ┌─────────────────────────────────────┐
        │  Frameworks & Drivers (DB, web, MQ)  │   ← outermost: volatile details
        │   ┌─────────────────────────────┐   │
        │   │   Interface Adapters        │   │
        │   │   ┌─────────────────────┐   │   │
        │   │   │   Use Cases          │  │   │
        │   │   │   ┌─────────────┐   │   │   │
        │   │   │   │  Domain      │  │   │   │   ← innermost: pure, stable
        │   │   │   │  (Entities)  │  │   │   │
        │   │   │   └─────────────┘   │   │   │
        │   │   └─────────────────────┘   │   │
        │   └─────────────────────────────┘   │
        └─────────────────────────────────────┘
              dependencies point INWARD →
  • The domain at the center knows nothing about anything outside it - no database, no HTTP, no Spring.
  • Use cases depend on the domain, nothing further out.
  • The outer ring (Postgres, Spring MVC, Kafka) depends inward on the abstractions the inner rings define.

The database is now a detail at the edge, not the center. The web framework is a delivery mechanism you could swap for a CLI. The domain doesn't know they exist.

How the arrow gets inverted

But the domain needs to save data - doesn't that mean it depends on the database? No, and this is the crux: the domain defines an interface (a "port") for what it needs, in its own terms, and the outer layer implements it. Recall Dependency Inversion:

// INNER ring (domain/use-case) - defines what it needs, owns the interface
interface AccountRepository { Optional<Account> findById(AccountId id); void save(Account a); }

// OUTER ring (infrastructure) - implements it, depends INWARD on the domain's interface
class JpaAccountRepository implements AccountRepository { /* JPA details live here */ }

The dependency arrow points from JpaAccountRepository inward to AccountRepository. At runtime, dependency injection supplies the concrete adapter, but at compile time the domain depends on nothing outward. That single inversion, applied systematically, is the whole architecture.

Hexagonal, Onion, Clean - same rule, different diagrams

Don't get lost in the names. Alistair Cockburn's Hexagonal (Ports & Adapters), Jeffrey Palermo's Onion, and Robert Martin's Clean Architecture are three drawings of the same principle: keep the domain pure at the center and let dependencies point inward. Hexagonal emphasizes the symmetric ports/adapters at the boundary; Clean emphasizes the concentric layers. This module uses both vocabularies because you'll meet both - but it's one idea.

A stage play: the script vs. the theatre

The domain is the script of a play - the story, characters, and lines. The infrastructure is the theatre: the lighting rig, the sound system, the particular building. A good script depends on none of them - it can be performed in a grand opera house, a school gym, or as a radio drama, because the story doesn't know or care about the venue. A bad production welds the script to one building's quirks ('exit stage left through the third door') so it can never move. The Dependency Rule says: write the script so the theatre depends on it, never the reverse. Swap the venue (Postgres → Mongo, web → CLI) and the story plays on, untouched.

Which way does the arrow point?

In ledger-legacy, the FeeCalculationService (core business logic) directly imports a JPA @Entity class TransactionRow and calls entityManager.persist(...). A new requirement says fees may need to be calculated for transactions coming from a CSV import that never touches the database. Explain how this violates the Dependency Rule and what the fix looks like at the level of dependency direction.

What is the single core rule of hexagonal/clean architecture?

Key takeaways

  • Hexagonal, Onion, and Clean architecture share one law: source-code dependencies point only inward, toward the domain.
  • Typical layered apps put the database at the center and make the domain depend on infrastructure - chaining stable logic to volatile details, backwards.
  • Inverting it puts a pure, framework-free domain at the center; the database and web framework become pluggable details at the edge.
  • The domain defines interfaces (ports) for what it needs; outer-ring adapters implement them, so the dependency arrow points inward (Dependency Inversion at system scale).
  • At runtime, DI supplies the concrete adapter, but at compile time the domain depends on nothing outward.
War diese Lektion hilfreich?
Diese Seite auf GitHub bearbeiten