skip to content

Compare layered (n-tier) architecture with hexagonal architecture (ports and adapters): which way do dependencies point in each, and which quality attributes does each optimize?

level: middleimportance: should knowfreq 58%

answer

  1. Layered: calls down, domain depends on DB
  2. Hexagonal: dependencies inward via ports
  3. Driving vs driven adapters
  4. Sinkhole anti-pattern
  5. Leaked ORM entities = hexagonal in name only

basics

~20 s

In layered architecture, calls flow downward — UI depends on business logic, which depends on the database layer, so the domain ends up depending on infrastructure. In hexagonal, all dependencies point inward to the domain; the database and UI are interchangeable adapters plugged into interfaces (ports).

solid answer

~50 s

**Layered** stacks presentation → business → persistence → database, each layer calling only the one below ("closed layers"). It is easy to understand and cheap to start, but the business layer transitively depends on persistence technology, which makes testing require a database and makes technology swaps invasive. It also tends toward "sinkhole" layers that just pass calls through, and it scales and deploys as one unit. **Hexagonal** inverts that: the domain core defines **ports** (interfaces expressing what it needs or offers) and infrastructure supplies **adapters** implementing them. The dependency rule is that source-code dependencies point only inward, achieved through dependency inversion. Driving adapters (HTTP, CLI, tests) call inbound ports; the core calls outbound ports implemented by driven adapters (repositories, message publishers, third-party clients). Layered optimizes simplicity and cost; hexagonal optimizes testability, technology independence, and long-lived domain code, paying with more indirection and mapping. They are not exclusive: hexagonal is commonly used *inside* each module of a modular monolith or inside each microservice.

code

pseudocode · 21 lines
pseudocode
// Hexagonal: the port is owned by the domain core
package domain
interface OrderRepository {            // outbound port
    fun save(order: Order)
    fun findById(id: OrderId): Order?
}

class PlaceOrder(private val repo: OrderRepository) {  // inbound use case
    fun handle(cmd: PlaceOrderCommand) {
        val order = Order.create(cmd.items)   // pure business rules, no framework
        repo.save(order)
    }
}

// Infrastructure depends on the domain, never the reverse
package infrastructure
class SqlOrderRepository(private val db: Database) : domain.OrderRepository { ... }
class InMemoryOrderRepository : domain.OrderRepository { ... }   // used by unit tests

// Layered equivalent, for contrast: OrderService imports the persistence package
// directly, so the business rules cannot be exercised without that technology.

go deeper

for a junior

Get the dependency direction right: layered goes down to the database, hexagonal points inward to the domain with the database as a plugin. Mention that hexagonal makes testing easier.

for a middle

Add the port/adapter vocabulary (driving vs driven), name the quality attributes each optimizes, and mention the sinkhole anti-pattern and the cost of mapping.

for a senior

Discuss the spectrum between them, how dependency inversion achieves the reversal, how leakage silently negates the benefit, and how hexagonal composes with monolith/microservice deployment choices.

for a principal

Cover when the ceremony is not worth it, how to enforce the dependency rule with automated architecture tests, the relationship to Onion/Clean/vertical slices, and how the choice interacts with team structure and expected system lifetime.

### Layered (n-tier) in detail Components are horizontal slices by *technical concern*: ``` Presentation (controllers, views) ↓ Business (services, domain rules) ↓ Persistence (DAOs/repositories, ORM mapping) ↓ Database ``` **Closed layer** — a request must pass through it; you may not skip. This is *layers of isolation*: a change in one layer cannot ripple past its neighbors. **Open layer** — may be bypassed (a shared services layer, say). Openness buys speed and costs isolation, so it should be a deliberate, documented exception. Strengths: near-universally understood, trivially navigable, cheap, great for small-to-medium systems and for teams organized by technical specialty. Weaknesses: - **Architecture sinkhole anti-pattern** — many requests pass through a layer that adds nothing but delegation. A rough rule: if the large majority of requests are pass-through, the layering is costing more than it delivers. - **Dependency direction** — the business layer ends up importing persistence types (entities, session objects, query APIs), so domain logic cannot be unit-tested or reused without the infrastructure. - **Deployability and scalability granularity** — one deployment unit; a change anywhere means redeploying and retesting everything; you scale the whole stack even if only one capability is hot. - **Slices cut across layers** — a single feature touches every layer, so "a small change" is rarely small and cross-team conflicts are frequent. ### Hexagonal (ports and adapters) in detail Coined by Alistair Cockburn; the near-identical Onion and Clean architectures share its central rule. - **Domain core** — entities and use cases, expressed in the language of the business, importing no framework, no ORM, no HTTP. - **Port** — an interface *owned by the core*. Two kinds: - *Inbound/driving port*: what the application can do (`PlaceOrder`), called from outside. - *Outbound/driven port*: what the application needs (`OrderRepository`, `PaymentGateway`), implemented outside. - **Adapter** — a technology-specific implementation on either side: a REST controller or CLI or test harness on the driving side; a SQL repository, message publisher, or HTTP client on the driven side. - **The dependency rule** — all source-code dependencies point inward. Runtime control still flows outward (the core calls the database), but the *compile-time* direction is inverted via the interface, which is the whole trick (Dependency Inversion Principle). Strengths: the domain is unit-testable with in-memory fakes and no container; infrastructure choices can be deferred and swapped; the same core serves HTTP, a batch job, and tests unchanged; the business language is not polluted by technology. Costs: more types and indirection; explicit mapping between domain models and persistence/DTO models (people either write mappers or leak the model and lose the benefit); newcomers must learn where things live; over-applied to a CRUD app with no real domain logic, it is pure ceremony. ### The key contrast | | Layered | Hexagonal | |---|---|---| | Organizing axis | technical concern (horizontal) | domain inside, technology outside | | Dependency direction | downward, domain → infrastructure | inward, infrastructure → domain | | Unit-testing the rules | usually needs a DB or heavy mocking | trivial with in-memory adapters | | Swapping the datastore | invasive | replace one adapter | | Learning curve | very low | moderate | | Best when | simple system, cost/speed dominate | domain logic is the asset, long life expected | ### Edge cases and honest caveats - **"Layered with a repository interface in the domain" is already halfway to hexagonal.** The moment the interface is *owned by* the business layer and implemented by persistence, you have inverted the dependency. The styles form a spectrum. - **The hexagon shape means nothing.** Cockburn chose six sides only to have room for multiple ports; it does not imply six of anything. - **Leakage kills the benefit.** If ORM-annotated entities are returned from the core, or repository ports take framework query objects, you have hexagonal folder names and layered coupling. This is the single most common failed adoption. - **Neither style addresses deployment.** Both are internal-structure styles. You still separately decide monolith vs services, sync vs event-driven — which is why hexagonal-inside-a-microservice is such a common pairing. - **Vertical slice architecture** is a third option worth naming: organize by feature rather than by layer, so a change touches one folder. It attacks layered's cross-cutting-change weakness from a different angle. ### Answering well Lead with the dependency direction — it is the crux. Then name the quality attributes each buys, note that hexagonal's cost is indirection and mapping, and finish by observing that they are complementary rather than competing, with layered being perfectly appropriate for simple systems.

  • How do Onion and Clean architecture relate to hexagonal?
    They are restatements of the same dependency rule with more prescribed rings. Onion adds concentric layers (domain model, domain services, application services, infrastructure); Clean adds entities/use cases/interface adapters/frameworks plus explicit boundary-crossing objects. All three enforce inward-pointing source dependencies; the differences are naming and how many rings are mandated.
  • What is the most common mistake when adopting hexagonal architecture?
    Letting infrastructure types leak into the core — returning ORM entities from ports, putting framework annotations on domain objects, or defining ports in terms of SQL or HTTP concepts. The folder structure looks hexagonal while the coupling stays layered, so you pay the indirection cost and get none of the testability benefit.
  • When is layered architecture genuinely the better choice?
    When the system is small, the domain is mostly CRUD with little invariant logic, the team is small or junior-heavy, and cost and speed rank above testability and technology independence. Introducing ports and mappers around logic that is just 'validate and store' adds ceremony without buying anything.

Layered is a building where every floor rests on the one below, so replacing the foundation means touching everything above it. Hexagonal is a stage: the play (the domain) is written first, and lighting, sound, and audience seating are equipment plugged into standard sockets — swap the projector without rewriting the script.

context