Explainstuff.mebeta
All concepts
Architecture Stylesintermediate8 min

Layered & Hexagonal Architecture

Put your business rules at the center and treat the UI, database, and outside world as replaceable plug-ins — so the core of your app stays clean, testable, and free of framework baggage.

Imagine the brains of your application — the rules that decide what a valid order is, how a discount is calculated, when an account is overdrawn — locked safely in a vault at the center of the building. The web pages, the database, the payment provider, the email service: those are all just doors and hallways around the vault. You can knock down a wall and add a new entrance without disturbing what's inside.

That mental picture is what Hexagonal architecture and Clean Architecture are reaching for. Both styles insist that your business logic belongs at the protected center, and that every framework, screen, and external service is a swappable detail living at the edge.

The problem

In a lot of codebases the business logic is hopelessly entangled with the things around it. The rules are smeared across controllers that know about HTTP, classes that import the ORM, and methods that call a vendor's SDK directly. The code that decides what should happen is woven into the code that handles how it talks to the outside world.

That entanglement is expensive. You can't test a calculation without spinning up a database, you can't reason about a rule without understanding the framework, and swapping a dependency — a new database, a different message broker, a fresh UI — means picking the logic apart and rewriting it. The core, the most valuable part of your app, ends up being the hardest part to change.

How it works

Both styles start by drawing a ring around the domain and application logic and declaring it the center. Everything external — the UI, the database, messaging, third-party APIs — is pushed to the outer edges and treated as an adapter that plugs into a port. A port is just an interface the core owns ("give me a way to save an order"); an adapter is a concrete implementation that fulfills it ("here's the Postgres version"). The core talks to ports and never to a specific technology.

The rule that makes this hold together is the Dependency Rule: source-code dependencies only ever point inward. The core depends on nothing outside itself, and everything on the outside depends on the core. The animation below shows outer adapter layers — UI, database, external services — all reaching toward a stable core, with every arrow of dependency pointing in the same direction.

Dependencies point inward
depends on
UI adapter
DB adapter
Domain core
External
Adapters for the UI, database, and external services all depend on the core — never the reverse.
Note

"Hexagonal" has nothing to do with six sides. Alistair Cockburn drew the diagram as a hexagon simply to give himself room for several ports around the edge — input ports for driving the app, output ports for the things it drives. The shape isn't meaningful; the name people actually use, Ports and Adapters, describes the idea far better.

How dependencies point inward

At first the Dependency Rule sounds impossible: the core obviously needs to save data, so how can it not depend on the database? The trick is dependency inversion. Instead of the core importing the database, the core defines a port — an interface like OrderRepository — and the database adapter implements it. The arrow flips: the adapter now depends on the core's interface, not the other way around.

The matching piece at runtime is dependency injection: some outer wiring layer creates the concrete adapter and hands it to the core, which only ever sees the interface it defined. This is what keeps coupling low — the core is sealed off from concrete technologies and depends only on abstractions it controls.

Why the payoff is worth it

Because the core depends on nothing external, you can test it in complete isolation. There's no database to start, no web server to boot — you just hand the core fake adapters (in-memory implementations of its ports) and exercise the rules directly. Tests run in milliseconds and stay focused on behavior rather than plumbing.

The same isolation makes infrastructure swappable. Moving from one database to another, replacing a payment provider, or putting a CLI in front of code that used to serve a web UI all become edge-of-the-system changes. You write a new adapter that satisfies the existing port, and the core never notices the difference.

Contrast with traditional N-tier

It helps to compare this with classic N-tier layering, where the presentation tier sits on top of the business tier, which sits on top of the data tier. In that stack, dependencies flow downward, which means the domain layer typically depends on the database layer beneath it — the business rules import the data-access code. The most important layer ends up coupled to the least stable one.

Hexagonal and Clean Architecture invert that relationship. The domain sits at the center with nothing below it, and the database becomes an outer adapter that depends on the core instead of the core depending on it. Same goal of separating concerns, but the arrows point the other way — toward stability rather than toward infrastructure.

Watch out

This style is not free. All those interfaces, adapters, and wiring layers add indirection and boilerplate, and tracing a single request from the edge to the core touches more files than it would in a simple app. For a small CRUD service or a short-lived prototype, that ceremony is pure overhead — reach for it when the business logic is rich and long-lived enough to be worth protecting.

When to use it

Hexagonal and Clean Architecture shine when the business logic is the hard, valuable part of the system and is expected to live for years: domain-heavy applications, systems that must outlast the frameworks they were built on, and codebases where thorough, fast testing of the rules is a priority. They're also a good fit when you genuinely expect to swap infrastructure — multiple data stores, several front ends, or external providers you don't trust to stay.

Lean away from the style when the app is small, mostly CRUD, or unlikely to change much. In those cases the indirection costs more than it saves, and a straightforward N-tier layout will serve you better. The discipline is worth adopting precisely when the cost of not protecting your core would eventually dwarf the cost of the extra structure.

Key takeaways

  • Hexagonal architecture (Ports and Adapters) and Clean Architecture both put the domain and business rules at the center, pushing the UI, database, and other external systems out to the edges.
  • External systems connect through ports (interfaces the core defines) and adapters (implementations that fulfill those interfaces), so the core never knows or cares which technology is on the other side.
  • The Dependency Rule is the heart of the style: source-code dependencies always point inward — the core depends on nothing external, and everything external depends on the core.
  • Dependency inversion and injection make the rule possible by handing concrete adapters to the core at runtime, which keeps coupling low and lets you swap infrastructure freely.
  • The payoff is a core you can test in isolation and infrastructure you can replace, at the cost of more indirection and boilerplate — overkill for tiny apps but valuable for long-lived, business-heavy systems.

Keep going