Clean, Hexagonal, Onion: Three Names for One Idea
Layered, Hexagonal, Onion and Clean are not four architectures competing for your project. They are one move — domain in the centre, dependencies pointing inward — plus four vocabularies. Here is the shared skeleton, the few differences that are real, and where the actual decisions live.
A team spends a week deciding between Hexagonal and Clean Architecture. Diagrams get drawn. Someone mentions Onion. Someone else has strong feelings about whether the thing in the middle is called an entity or a domain object.
At the end of the week, no decision has been made that would change a single line of code.
They were not choosing an architecture. They were choosing a diagram — and the four candidates all draw the same one.
The one idea
Strip the vocabulary and every one of these reduces to a single rule:
Source-code dependencies point inward only — always toward higher-level policy, never away from it.
That is it. Your business rules sit in the middle. The database, the web framework, the message broker and the UI sit outside. Nothing in the middle is allowed to know anything about the outside.
The mechanism that makes this possible in every variant is dependency inversion. The domain needs to save an order, so the domain declares the interface — OrderRepository — and infrastructure implements it. The arrow that would naturally point from domain to database gets turned around. That inversion is the whole trick, and all four names are built on it.
┌─────────── infrastructure ───────────┐
│ HTTP Postgres Kafka │
│ │ ▲ ▲ │
│ ▼ │ │ │ ← every arrow points IN
│ ┌──────────────────────────────┐ │
│ │ domain / use cases │ │
│ │ (defines the interfaces) │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────┘
If your code does that, you are doing all four. If it does not, no diagram will save you.
Why "layered" started it, and why it got superseded
The lineage matters, because it explains why there are four names for one thing.
Layered architecture came first: UI on top, then application, then domain, then infrastructure, with dependencies pointing down and the domain knowing nothing above it. It was a genuine advance — it made the domain unit-testable and gave the model a place to live.
But it has a flaw baked into the picture. To keep infrastructure below the domain while the domain declares what it needs, you already have to invert the dependency with interfaces. The layer diagram tells you the domain sits on top of infrastructure; the actual dependency graph says the opposite. The metaphor fights the mechanism.
Ports and adapters fixed the metaphor rather than the technique. Nothing about the code has to change; what changes is that the picture finally matches the dependency graph. There is no privileged top or bottom, only inside and outside — and once you draw it that way, the question "which layer does this go in?" stops having a confusing answer. That is why the ports-and-adapters framing largely displaced the layer diagram: it is a better description of the same practice.
What each name actually emphasises
The differences are real but small, and they are differences of emphasis, not of idea:
- Hexagonal (Ports & Adapters) stresses symmetry. Inbound (HTTP, CLI, tests) and outbound (database, messaging) are both just adapters plugging into ports. The hexagon has no top. This maps unusually well onto a system that serves REST and consumes events.
- Onion stresses concentric rings with the domain model innermost — the same inward rule, drawn as circles rather than a polygon.
- Clean stresses use cases as first-class citizens and names the constraint explicitly as the Dependency Rule. It is the most general of the four — hexagonal and onion are realisations of the same constraint wearing different diagrams.
Pick whichever your team already has words for. The one you can explain in a code review beats the one that scored higher in a blog post.
Where the real decisions live
Here is what actually varies between two teams both "doing hexagonal" — and this is where your week would have been well spent:
How many boundaries do you actually draw? Every port is an interface, a test double, and a place for a bug to hide. Two well-chosen boundaries beat seven speculative ones.
Do you map across the boundary, or pass the object through? Strict versions convert to a DTO at every crossing. Pragmatic ones let a domain object cross into the adapter. Mapping buys isolation and costs a translation layer that must be written, tested, and kept in sync.
Do framework types leak through your ports? This is the failure that quietly voids the whole thing. A port that returns an ORM entity, or takes an HTTP request object, has not inverted anything — it has just added a file. The isolation is only as real as the types on the interface.
How much ceremony does this module deserve? Which is the question the naming argument was avoiding.
The honest cost
All four add indirection, interfaces, and mapping code. On a thin CRUD service, that ceremony is pure overhead — the domain has no logic to protect, and you have bought isolation from a change that will never come. The warning applies to all four equally, and it is the one most often skipped: do not over-formalise a simple CRUD app.
The rule of thumb worth carrying: spend boundary ceremony where the thing inside is expensive to get wrong and likely to change. A pricing engine, a claims workflow, a rules-heavy core — yes. A table with six columns and a REST wrapper — no. Quality is a requirements decision, and architecture is no exception.
The takeaway
Three questions replace the week-long argument:
- Do source dependencies point inward, with the domain defining its own interfaces? If yes, you are doing all four, whatever you call it.
- Which boundaries earn their keep here? Name them individually and justify each. That is the real design work.
- What do we call it? Whatever the team already knows. Genuinely — this is the least consequential question in the room, and it is the one that eats the week.
The diagrams differ. The idea does not. Argue about your boundaries instead; those are the part that will still matter in a year.