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.