The Dependency Rule
The one idea behind hexagonal, onion, and clean architecture: source-code dependencies point only inward, toward the domain - so the core never depends on the database or web layer.
On this page
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.
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.
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.