← Back to the section

In the classic three-layer architecture the layers run top to bottom: UI → business logic → data access layer. Dependencies point downward: business logic depends on the data layer, which depends on the database. As a result, the domain is tightly coupled to a specific DBMS and framework: you can't test the logic without a running database, and switching storage drags edits into the business code. Onion Architecture flips this around: at the center sits a pure domain, while everything infrastructural is pushed outward and depends on the center, not the other way around.

What Onion Architecture is

Picture an onion cut in half: concentric rings, with the domain model at the very center. Each ring may depend only on the rings that are inside it. Outer rings know about inner ones; inner rings know nothing about the outer ones.

What the diagram shows: dependency arrows point only inward, toward the domain.

diagram

The rings from the center outward:

  • Domain model — entities and value objects with business rules. Pure code: no imports of the database, HTTP, or framework.
  • Domain services — logic that spans several entities and doesn't fit inside a single one.
  • Application services (use cases) — orchestration: accept a request, invoke the domain, save the result.
  • Outer ring — everything else: the database, queues, external APIs, web controllers, framework configuration.

The dependency rule

There is one main rule: dependencies point only inward. The outer ring knows about the inner ones; the inner ones know nothing about the outer.

How is this technically possible, given that an application service needs to save data to the database (the outer ring)? Through dependency inversion: the inner ring declares an interface (for example OrderRepository), and the outer ring implements it (PostgresOrderRepository). The domain depends on an interface it defined itself; the concrete implementation lives outside and is plugged in when the application is assembled. That way the dependency arrow turns inward, even though the data flow goes outward.

A direct consequence: the domain is testable without a database or framework — the interfaces are replaced with stubs.

Onion, Hexagonal, and Clean Architecture — one family

These are all variants of a single idea: keep the domain pure and turn the dependencies toward the center. What differs is mostly vocabulary and emphasis:

  • Hexagonal (Ports & Adapters) — the domain at the center, surrounded by ports (interfaces) and adapters (implementations). It stresses the symmetry of inbound and outbound boundaries.
  • Onion — the same inward dependencies, but presented as concentric rings with the domain model at the very center; the emphasis is on layering.
  • Clean Architecture — the same thing, with rings "entities → use cases → interface adapters → frameworks" and the same dependency rule.

In practice they are interchangeable — pick the vocabulary your team has adopted. This site covers the hexagonal variant in detail (core, ports, adapters, architecture tests); if you need a step-by-step implementation, start there.

When to use it

Onion Architecture is justified when the domain is rich and long-lived, there are several kinds of infrastructure (DB + queue + external APIs), and testability matters. For a simple CRUD service without complex rules, concentric rings are excessive ceremony: you end up with lots of interfaces for the sake of one or two calls.

In short

  • Onion Architecture is concentric rings with the domain model at the center; dependencies point only inward.
  • The domain knows nothing about the database, HTTP, or framework — implementations of interfaces declared inside are plugged in from outside (dependency inversion).
  • The main payoff: the domain is testable without infrastructure, and swapping the DB/framework doesn't touch the business logic.
  • Onion, Hexagonal, and Clean Architecture are one family; they differ in vocabulary, not in essence.
  • For simple CRUD it's overkill; use it for a rich, long-lived domain.
  • Hexagonal Architecture — the same principle in the ports & adapters variant, with a step-by-step implementation.
  • CQRS — separating commands and queries on top of a pure domain.
  • Domain-Driven Design — what exactly lives at the center of the onion.