Onion Architecture
Domain model at the center, infrastructure on the outside β like Clean Architecture but with an explicit emphasis on domain services as the second ring.
Intent & Description
π― Intent
Protect domain logic from infrastructure details by coupling everything toward the center, never outward.
π Context
Similar to Clean Architecture but with a stronger emphasis on domain modeling. Your domain services and application logic need to be completely decoupled from database ORM objects, HTTP clients, and third-party SDKs.
π‘ Solution
Concentric rings, all dependencies point inward:
- Domain Model β core objects, state, and invariants. Pure domain.
- Domain Services β operations spanning multiple domain objects (e.g., a TransferService coordinating Account objects).
- Application Services β coordinates tasks, orchestrates domain services, handles transactions.
- Infrastructure β database implementations, web API controllers, logging, message queues β all implement interfaces defined in inner rings.
Infrastructure depends on the domain. Never the other way around.
Real-world Use Case
π TL;DR
Domain center, infrastructure shell. All arrows point inward. Closely related to Clean Architecture β pick one and commit. The mapping overhead is real but the decoupling payoff is bigger.
Advantages
- Domain core is infrastructure-agnostic and fully unit-testable without any external dependencies.
- Infrastructure details (database choice, messaging system) become implementation decisions, not architectural constraints.
Disadvantages
- Multiple projects or directories with extensive mapping code between layers adds real overhead.
- Teams unfamiliar with ports-and-adapters thinking have a steep learning curve.
// Core Domain
class OrderItem {
constructor(sku, quantity, price) {
this.sku = sku;
this.quantity = quantity;
this.price = price;
}
}
// Domain Service interface (implemented in Infrastructure layer)
class PaymentGateway {
async process(amount) { throw new Error("Not implemented"); }
}