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"); }
}