Nguyen Le Phong

Foundations of Software ArchitecturePart 2 of 6

Clean, Onion & Hexagonal Architecture: One North Star, Three Names

They look like three rival architectures. They are really the same idea — dependencies pointing inward, frameworks at the edge — drawn three ways. Here is what each one actually adds, and which to reach for.

If you've spent any time reading about software architecture, you've probably run into three names that keep appearing together: Hexagonal Architecture, Onion Architecture, and Clean Architecture. They each have their own diagrams, their own vocabulary, and their own passionate advocates. Reading about all three in one sitting can feel like watching three people argue about the best way to say the same sentence.

Here's the honest version: they are the same idea, discovered independently at different times, drawn with slightly different pictures. Each one adds a small nuance the others don't emphasise as loudly. Once you see the shared North Star, the differences become obvious — and you can pick whichever vocabulary your team already likes best.

The one idea all three share

Every one of these architectures rests on a single rule, sometimes called the Dependency Rule: source-code dependencies must always point inward, toward the business rules, and never outward toward frameworks, databases, or I/O.

Think of it like gravity. Your domain — the logic that makes your product worth using — sits at the centre. Everything around it (databases, web frameworks, payment SDKs, email vendors, CLI tools) orbits on the outside. The outer layers are allowed to know about the inner layers. The inner layers are absolutely forbidden from knowing the outer layers exist.

Why does this matter? Because the things that change most often are on the outside: you switch payment providers, migrate to a new database, rewrite the frontend, add a mobile app. If your business rules are entangled with those details, every change ripples through the code you care about most. If they're insulated, a vendor change is a contained swap at the edge — the core never notices.

Alistair Cockburn, Jeffrey Palermo, and Robert C. Martin each spotted this pattern from a different angle. Cockburn drew a hexagon and called the connection points "ports". Palermo drew concentric rings and called them "layers". Martin drew concentric circles and added strict names to the layers. The arrow directions in every diagram point the same way: inward.

The one rule

Source-code dependencies point inward. The domain defines interfaces; the outside world implements them. Flip that direction anywhere and you've broken the architecture — no matter which name you're using.

Hexagonal Architecture (Ports & Adapters)

Alistair Cockburn introduced Hexagonal Architecture in 2005 under the alias Ports & Adapters. The core insight is symmetric: your application has two kinds of outside — things that drive it (a user clicking a button, a test runner, a scheduled job) and things it drives (a database, an email provider, a payment API). Both sides communicate through ports — interfaces the domain owns — and adapters — the concrete implementations that connect real technologies to those interfaces. The hexagon shape is just a visual hint that there are many such ports, not just "top" and "bottom". If you want the full treatment of ports, adapters, primary/secondary sides, and worked code examples, the previous article in this series — Ports & Adapters — covers it from first principles. The headline takeaway here is that Hexagonal is the most symmetric of the three: it treats incoming calls and outgoing calls with equal rigour, and its vocabulary (port, adapter, driving, driven) is the most concrete and mechanical.

Onion Architecture

Jeffrey Palermo described Onion Architecture in 2008. He kept the same inward-dependency rule but chose a different picture: concentric rings, like the layers of an onion. The innermost ring is the Domain Model — your core entities and value objects, pure business concepts with no technology in sight. Surrounding it is the Domain Services ring, which holds logic that coordinates multiple domain objects but still doesn't know anything about the outside world. Further out sits the Application Services ring, where use cases live and orchestration happens. On the very outside are the infrastructure and UI details — databases, web frameworks, external APIs.

What Onion emphasises that the other two don't shout about as loudly is the distinction between domain model and domain services. Palermo was writing for .NET teams who often confused "the thing that represents an Order" with "the service that processes Orders" — a genuinely useful separation. Onion also makes the layering very visual: you can look at any class's import list and immediately tell which ring it belongs to based on what it's allowed to import. If a domain service is trying to import a database library, it's obviously in the wrong ring.

Clean Architecture

Robert C. Martin (Uncle Bob) published Clean Architecture in 2017, synthesising ideas he'd been refining for years. He drew the same concentric rings but gave each one a firm name and a firm purpose. From the inside out: Entities (the enterprise-wide business rules — the stuff that would still be true if you had no computer at all), Use Cases (the application-specific business rules — exactly what the software is supposed to do for a user), Interface Adapters (controllers, presenters, gateways — the code that translates between the use-case world and the outside world), and Frameworks & Drivers (the outermost ring — databases, web frameworks, UI libraries, device drivers).

The most distinctive addition Clean Architecture makes is the explicit, named Use Cases layer. In many codebases "use case" is an informal idea — you might have a UserService that does ten different things. Martin insists on making each use case a first-class, named artefact: a class or function whose name literally says what it does (PlaceOrder, RegisterUser, GenerateInvoice). He calls this a "screaming architecture" — when a new developer opens the project structure, they should immediately see what the system does, not what framework it uses. Martin also formalized the concept of Boundaries — explicit interface seams at every ring crossing — and the idea that data crossing a boundary must be serialisable, simple data structures, not rich domain objects carrying hidden behaviour.

Screaming architecture

Martin's phrase "screaming architecture" captures a simple aspiration: open any folder in the project and you should immediately understand the business intent, not the tech stack. A folder called use-cases/ containing PlaceOrder.ts and RegisterUser.ts screams its intent. A folder called controllers/ containing everything else whispers nothing useful.

Side by side: mapping the vocabulary

The biggest practical hurdle when reading about all three is that they use different words for overlapping concepts. Here's the translation table:

Concept Hexagonal (Cockburn) Onion (Palermo) Clean (Martin)
The innermost core Domain (no specific ring names) Domain Model ring Entities ring
Business use cases Application logic inside the hexagon Application Services ring Use Cases ring (explicit, named)
Translation layer Adapters (driving + driven) Infrastructure ring Interface Adapters ring
Outermost tech Outside the hexagon (HTTP, DB, etc.) UI / Infrastructure ring Frameworks & Drivers ring
Connection seam Port (interface owned by the domain) Implicit interface at each ring boundary Boundary (explicit interface at each crossing)
Primary emphasis Symmetric ports; testability; replaceability of every adapter Layered rings; domain model vs domain services distinction Explicit named use cases; screaming structure; strict data boundaries
Best when… You have many integrations and need each adapter independently replaceable Rich domain models with non-trivial service orchestration Large teams that need a shared vocabulary and a "screaming" folder structure

Where they genuinely differ in practice

The shared rule hides real differences in emphasis that show up when you sit down to write code.

Hexagonal is the most mechanical. It gives you precise, named slots: here is the driving adapter, here is the port it calls, here is the use case that handles it, here is the output port, here is the driven adapter. If you follow those slots faithfully, the structure almost writes itself — and because both sides are symmetric, the discipline that keeps you from letting a database sneak into the domain is the same discipline that keeps the domain from leaking into the HTTP layer. The tradeoff is that Hexagonal doesn't tell you how to structure the interior of the hexagon — it's silent on whether your core should have sub-layers like domain model vs domain services.

Onion is the most layered. Where Hexagonal draws one boundary (inside the hexagon vs outside), Onion draws several rings inside the core. This is valuable when your domain is large enough to need its own internal structure — distinguishing pure entities from domain services that coordinate them, and domain services from application services that orchestrate use cases. The ring metaphor is visually intuitive, but the stricter you are about it, the more discipline you need: it's tempting to reach across rings when you're in a hurry, and Onion doesn't give you a mechanical check the way Hexagonal's port/adapter vocabulary does.

Clean is the most prescriptive. Martin names every layer and every concept, which is both a strength and a source of debate. The strength is that teams share an unambiguous vocabulary — when two engineers discuss a "use case boundary" everyone knows what that means. The source of debate is that the prescribed structure can feel heavy for small applications: creating explicit InputBoundary, OutputBoundary, and UseCase interfaces for every feature is a lot of ceremony when you're building a prototype. Clean Architecture also insists on plain-data transfer objects (DTOs) crossing every ring boundary, which adds code but makes the seams very explicit. The payoff comes at scale, when preventing "rich object leakage" across layers becomes a real problem rather than a hypothetical one.

In short: Hexagonal gives you the clearest mechanical guidance on how to wire things together. Onion gives you the clearest internal layering of the domain. Clean gives you the clearest vocabulary and the most opinionated folder structure. Most real-world codebases blend all three without realising it.

The same inward-dependency idea drawn as concentric rings — identical to the hexagon from part 1, just a different shape. Domain Entities / Model Use Cases / Domain Services Adapters / Interface Adapters / Application Services Frameworks · Databases · UI · External APIs Dependencies always point inward — the same rule as the hexagon in part 1.
All three architectures draw this same picture: an innermost domain untouched by technology, surrounded by rings that add detail outward, with every dependency arrow pointing inward. Hexagonal draws it as a hexagon with labelled ports; Onion and Clean draw it as concentric circles with named rings. The shape changes; the arrow direction never does.

Which one to reach for

Because all three share the same core rule, choosing between them is really a question of vocabulary, team size, and which nuances your situation calls for.

Team & stage Suggested approach Why
Solo / early startup Hexagonal, lightly. One or two ports around the database and the most volatile vendor. Speed matters more than completeness. A couple of interfaces give you fast tests and a swap path; the full ceremony can wait.
Small team, growing (≈ Series A) Onion or Hexagonal with explicit port interfaces across all I/O. Tests are becoming a bottleneck, and new hires need to understand the domain fast. Ring labels (or port names) make the structure legible at a glance.
Mid-size (multiple squads) Clean Architecture vocabulary as a shared contract between teams. Squads step on each other when boundaries are fuzzy. Naming every use case and every boundary gives teams a clean seam to work on independently.
Large enterprise All three together, with explicit boundaries, versioned contracts, and multiple adapters per port. Regulatory requirements, legacy systems, and multi-vendor procurement all demand the explicitness that only full Clean + Hexagonal boundaries provide.

A few real-world patterns that match this table:

A fintech startup (8 engineers) used two ports — PaymentGateway and LedgerRepository — and left everything else direct. When they switched payment providers eighteen months in, it was a single new adapter class and a one-line wiring change. The rest of the codebase was untouched. That's Hexagonal at its leanest and most effective.

A mid-size SaaS company (60 engineers) adopted Onion ring labels as a code-review rule: "nothing in the domain model ring imports from outside the ring, no exceptions". New engineers understood the boundary policy in their first PR review. The ring vocabulary became a shortcut — "you're reaching across rings here" was a complete review comment that everyone understood.

An enterprise bank (several hundred engineers, 20-year-old codebase) used full Clean Architecture vocabulary, with named UseCase interfaces, explicit DTO objects at every ring crossing, and two adapters per output port (old mainframe + new core-banking system running side by side). The ceremony was heavy, but auditors and compliance teams could read the architecture like a specification, and squads could deploy independently because the boundaries were real seams in the code.

Don't mix vocabulary without a glossary

The only genuinely bad outcome is using all three names interchangeably on the same team without a shared glossary. If half the team calls it a "port" and the other half calls it a "boundary", code review becomes a translation exercise. Pick one vocabulary, write it down, and stick to it. The underlying rule — dependencies point inward — is what matters.

Key takeaways

  • One rule, three diagrams. Hexagonal, Onion, and Clean Architecture all enforce the same Dependency Rule: source-code dependencies point inward, toward business rules, never outward toward frameworks or I/O.
  • Hexagonal (Cockburn, 2005) gives you the most mechanical guidance: ports and adapters, symmetric driving and driven sides. Best when you have many integrations to swap.
  • Onion (Palermo, 2008) adds explicit concentric rings inside the domain, separating the domain model from domain services from application services. Best when your domain is large enough to need internal structure.
  • Clean (Martin, 2017) names every layer and makes use cases first-class artefacts. Adds "screaming architecture" — the folder structure declares business intent. Best for large teams who need a shared vocabulary and strict DTOs at every boundary.
  • They are additive, not competing. Most mature codebases blend all three: Hexagonal port/adapter wiring, Onion ring discipline inside the domain, Clean use-case naming at the application layer.
  • Match the dose to the team. A two-port Hexagonal setup is enough for a startup. Full Clean ceremony pays off at enterprise scale. Don't pay for vocabulary you don't need yet.
  • Pick one vocabulary per team and write it down. The name matters less than the consistency.

The next article in this series goes one level deeper into the mechanism that makes all three possible: Dependency Injection & Inversion of Control — the practical wiring that connects adapters to ports at runtime without the domain ever touching a framework.