Skip to content

Application-Layering Architectural Patterns

These patterns answer a different question than the presentation patterns: not how does the UI talk to logic?, but how is the entire application divided into layers, and which direction do dependencies point?

The first — Layered (N-tier) — is the natural starting point. The next three — Hexagonal, Onion, Clean — are increasingly prescriptive variations on a single core idea: the domain should not depend on infrastructure; infrastructure should depend on the domain.

Layered / N-tier Architecture

The default for most enterprise software since the 1970s. The app is sliced horizontally into layers — typically Presentation → Business → Data Access → Database. Each layer talks only to the one directly below it.

flowchart TD
    Presentation[Presentation Layer<br/>Controllers, Views]
    Business[Business Layer<br/>Services, Domain logic]
    Data[Data Access Layer<br/>Repositories, ORM]
    DB[(Database)]

    Presentation --> Business
    Business --> Data
    Data --> DB

When to use

  • Most CRUD-heavy business apps.
  • Teams new to layering — it's the easiest pattern to learn and enforce.
  • Codebases where the database schema and domain model are very similar.

Trade-offs

  • Pro: Universally understood; matches how most frameworks (Rails, Spring, Laravel) ship out of the box.
  • Pro: Easy to onboard new developers.
  • Con: Domain depends on infrastructure — the Business layer imports the Data layer, which imports the database. Swapping the DB or testing without it is painful.
  • Con: Tends to leak DB concerns (table shapes, ORM annotations) into business logic.

Hexagonal Architecture (Ports & Adapters)

Coined by Alistair Cockburn in 2005 (originally proposed in 1994). The domain sits at the center, surrounded by ports (interfaces it owns) and adapters (concrete implementations of those ports). Anything outside — database, HTTP, message queue, UI — is just an adapter plugged into a port.

flowchart LR
    UI[HTTP / UI<br/>Adapter]
    Tests[Test Driver<br/>Adapter]
    Domain[Domain Core<br/>+ Ports]
    DB[Database<br/>Adapter]
    MQ[Message Queue<br/>Adapter]

    UI --> Domain
    Tests --> Domain
    Domain --> DB
    Domain --> MQ

The key insight: the domain defines the port (interface); the adapter implements it. Dependencies point inward — toward the domain.

When to use

  • Backend services with multiple I/O channels (HTTP + queue + cron + CLI).
  • Codebases where you want to swap infrastructure (e.g., Postgres → DynamoDB) without touching domain logic.
  • Test-heavy projects — adapters are trivially mockable.

Trade-offs

  • Pro: Domain is fully isolated from frameworks and I/O. Tests run without a database.
  • Pro: Adding a new I/O channel is an additive change (write a new adapter).
  • Con: More upfront design work than a layered app.
  • Con: Overkill for simple CRUD where the database is essentially the domain.

Onion Architecture

Introduced by Jeffrey Palermo in 2008. Concentric circles — Domain Model at the center, then Domain Services, Application Services, and finally Infrastructure & UI on the outside. The dependency rule: source code dependencies point only inward.

flowchart LR
    subgraph Outer["Infrastructure / UI"]
        subgraph AppSvc["Application Services"]
            subgraph DomSvc["Domain Services"]
                Core["Domain Model"]
            end
        end
    end

It's essentially Hexagonal Architecture restated as concentric layers, with explicit names for each ring.

When to use

  • DDD-style backends, especially in the .NET community where Onion is the de-facto vocabulary.
  • Teams that prefer "ring-by-ring" thinking over "ports & adapters" thinking.

Trade-offs

  • Pro: Strong separation between domain and infrastructure.
  • Pro: Visual clarity — every developer can point to which ring a class belongs in.
  • Con: In practice nearly identical to Hexagonal — choosing between them is mostly aesthetic.
  • Con: Same complexity overhead as Hexagonal; same overkill risk on small apps.

Clean Architecture

Codified by Robert C. "Uncle Bob" Martin in 2012 (book Clean Architecture, 2017). Four concentric layers, each with a defined name and role:

flowchart LR
    subgraph L4["Frameworks & Drivers (DB, Web, UI, Devices)"]
        subgraph L3["Interface Adapters (Controllers, Presenters, Gateways)"]
            subgraph L2["Use Cases (Application Business Rules)"]
                L1["Entities<br/>(Enterprise Business Rules)"]
            end
        end
    end
Layer Contains Knows about
Entities Core business objects, invariants Nothing else
Use Cases Application-specific business rules Entities
Interface Adapters Controllers, Presenters, Gateways, ViewModels Use Cases + Entities
Frameworks & Drivers Web, DB, UI framework, devices Interface Adapters (and inward)

The Dependency Rule: source code dependencies must point inward. Inner layers know nothing about outer layers — not even their existence.

Inversion happens at the boundaries

When a Use Case needs to save data, it doesn't call the database directly. It calls an interface it owns (e.g., UserRepository), which the outer Frameworks layer implements. This is the Dependency Inversion Principle — the same SOLID principle — applied at the architectural scale.

When to use

  • Large, long-lived backends where you expect to swap frameworks or databases over the years.
  • Mobile apps where the same domain logic must run on iOS and Android with very different UI frameworks.
  • Codebases where you can afford the upfront cost in exchange for lower long-term coupling.

Trade-offs

  • Pro: Most prescriptive of the three "domain-centric" patterns — easy to enforce in code review.
  • Pro: Pairs naturally with VIPER (iOS) and similar UI patterns on the outside.
  • Con: Steepest learning curve. New engineers often produce noise (extra mappers, DTOs).
  • Con: Most over-applied architecture in modern software — many small apps adopt it cargo-cult style.

How They Relate

Hexagonal, Onion, and Clean all express the same core idea: the domain should not depend on infrastructure; infrastructure should depend on the domain. They differ mainly in vocabulary and how prescriptive they are about layer count.

flowchart LR
    Hex[Hexagonal<br/>2005] -- "renamed as rings" --> Onion[Onion<br/>2008]
    Onion -- "named the rings,<br/>made dependency rule explicit" --> Clean[Clean<br/>2012]
Hexagonal Onion Clean
Author Cockburn Palermo Martin
Core metaphor Hexagon with ports Concentric rings Concentric rings, named
Number of layers 2 (inside / outside) 3–4 unnamed rings 4 named rings
Prescriptiveness Low Medium High

Practical advice:

  • If your team already says "Hexagonal" — stay there. If they say "Onion" or "Clean" — same.
  • Don't argue about which one is "correct." They're 95 % the same idea.
  • The thing that matters is the Dependency Rule: dependencies point toward the domain, never away from it.

Application + Presentation patterns compose

These layering patterns are about the whole app. The presentation patterns are about the UI layer specifically. They compose:

  • A Flutter app can use BLoC for the UI and Clean Architecture for the layering.
  • A React app can use Redux plus Hexagonal on the backend.
  • An iOS app can use VIPER which is essentially Clean Architecture for the UI module.