Onion Architecture
Core Idea
Examples and diagrams in this page follow the shared Hypothetical Scenario.
Long lived systems face constant technology churn. Data stores change. Transport layers change. Frameworks change. Business rules should not change at the same pace.
Jeffrey Palermo proposed Onion Architecture to protect core business code from this churn. The domain model stays in the center. Dependencies point inward. Outer layers adapt infrastructure to core contracts.
Historical Context
Palermo published the model in 2008 and extended it in 2013. The articles responded to a common enterprise problem. Teams built systems around frameworks and databases. Core business code then became hard to test and hard to evolve.
The model was framed as a practical architecture pattern, not a strict framework. It can pair with Domain Driven Design. It can pair with CQRS. It can fit an n tier deployment.
Sources: Palermo (2008) and Palermo (2013)
The Problem It Solves
Traditional layered systems often place data access at the base. All higher layers couple to that base. The database choice then drives many code decisions. Core business policies leak into infrastructure classes.
This design creates recurring pain:
- Domain logic tied to persistence details
- Fragile tests that need infrastructure setup
- High rewrite cost after framework change
- Slow feature work from high coupling
Main Concept
Onion Architecture uses concentric rings. The center ring holds enterprise domain objects and rules. Next rings hold domain services and application services. Outer rings hold adapters for UI, persistence, messaging, and external systems.
The dependency rule is strict. All compile time dependencies move toward the center. Outer code knows inner code. Inner code does not know outer code.
Palermo stated a central rule in clear terms.
"Direction of coupling is toward the center."
Source: Jeffrey Palermo, 2008
Onion layer view. Core domain code stays isolated from infrastructure details.
How It Works
A request enters through an outer adapter such as an API controller. The controller calls an application service. The application service coordinates domain behavior. The domain model enforces invariants and business rules.
When persistence is needed, inner code calls an interface. An outer adapter implements that interface. Dependency inversion keeps the interface in an inner ring. Concrete infrastructure classes stay in outer rings.
Dependency rule view. Compile time direction points inward. Runtime control still enters from outside.
Designing a System with Onion Architecture
An architect usually starts with functional scope. Onion design starts with domain scope. The first design artifact is a domain map, not a framework map. That map identifies business capabilities, invariants, and core language terms.
After that map, ring contracts become explicit. Domain entities and value objects stay in the center. Application services define use case orchestration. Ports define required external capabilities such as persistence or messaging. Adapters implement those ports in outer rings.
A practical workflow for architects:
- Model domain capabilities and invariants.
- Define use cases and application service boundaries.
- Define ports for data, messaging, and external integrations.
- Place interface contracts in inner rings.
- Implement adapters in outer rings.
- Add architecture tests that reject outward dependencies from inner rings.
- Build integration tests at adapter boundaries.
This sequence gives stable design pressure. Core behavior receives priority from day one. Technical stack choices remain replaceable.
Challenges and Shortcomings
Onion Architecture gives strong separation, but no model is free of cost. Teams face a real learning curve in the first iterations. Small products can feel heavy with too many boundaries.
Common friction points:
- Extra interfaces that add ceremony in small codebases
- Mapping overhead between transport models and domain models
- Slow progress if teams skip shared vocabulary work
- Ring leakage when utility packages import infrastructure types
- Overuse of generic repositories that hide domain intent
Governance is a key factor. Without code review discipline, dependency direction drifts over time. Without architecture tests, ring boundaries degrade quietly.
Link to Existing Handbook Concepts
| Concept | Why? |
|---|---|
| Abstraction and Boundaries | This model aligns with Abstraction and Boundaries. Interfaces define boundary contracts across rings. |
| Modularity and Composition | This model aligns with Modularity and Composition. Each ring composes focused responsibilities. |
| Simplicity First | This model aligns with Simplicity First. Dependency direction limits blast radius during change. |
| Class Design | This model aligns with Class Design. Class visibility and message passing support clean ring boundaries. |
| Request path and adapter composition | !Request path and adapter composition |
Onion Architecture and Microservices
Onion Architecture and microservices solve different concerns. Onion defines internal service structure. Microservices define system level service boundaries and deployment units.
A useful design pattern is simple. Each microservice gets its own onion. Each onion keeps domain rules isolated from service infrastructure. Service communication happens through APIs or events at outer rings.
This pattern helps in large distributed systems:
- Service internals stay testable with low infrastructure coupling.
- Service contracts stay explicit at communication edges.
- Technology choices can differ per service with lower rewrite risk.
Risks still exist in microservice systems:
- Shared databases across services collapse boundaries.
- Shared domain libraries across services create tight coupling.
- Chatty service calls can move complexity from modules to network paths.
A stronger service design keeps bounded contexts explicit. Each service owns its domain model and persistence. Cross service interaction uses stable contracts and versioned messages.
Microservice composition view. Each service uses inward dependencies. Service edges expose explicit contracts.
Practical Notes
Onion Architecture is not mandatory for every codebase. Palermo noted this point in early discussion. Small applications with short lifetime may not need this structure. Long lived domains gain clear value from this separation.
Written by: Pedro Guzmán
See References for complete APA-style bibliographic entries used on this page.