skip to content

In Onion Architecture, why is the domain model placed at the center of the design instead of the database or UI?

level: juniorimportance: must knowfreq 55%

answer

  1. concentric rings
  2. dependency rule points inward
  3. domain has zero dependencies
  4. interfaces owned by the inside
  5. volatile infra vs stable business rules

basics

~20 s

Onion Architecture puts the business rules in the middle of the app, like the core of an onion, and everything else (database, web framework, UI) sits in layers wrapped around it. The middle never needs to know about the outer layers.

solid answer

~50 s

Onion Architecture organizes the codebase as concentric rings: the domain model (entities and business rules) sits at the center with zero dependencies on anything else. Around it sit domain services, then application services (orchestrating use cases), then the outermost ring of infrastructure — database, web framework, message brokers, UI. The rule is the Dependency Inversion Principle applied architecturally: source code dependencies only point inward, never outward. Outer rings depend on inner rings, never the reverse. The domain model doesn't import an ORM, HTTP library, or framework annotation. This matters because the database schema, the web framework, and third-party APIs change far more often — and for reasons unrelated to the business — than the actual business rules do. By keeping the core ignorant of those volatile details, you can swap a database or framework without touching business logic, and you can unit-test business rules with no infrastructure running at all.

go deeper

for a junior

Should be able to state that business logic sits in the center and infrastructure sits outside, and give one reason why (so the database can change without rewriting business rules). Doesn't need to explain interface placement mechanics precisely.

for a middle

Should correctly place repository/gateway interfaces on the inner ring and their implementations on the outer ring, and explain how dependency injection wires them together at runtime while imports still point inward.

for a senior

Should discuss trade-offs — ceremony cost for small apps, the risk of leaky abstractions, and enforcing the rule with architecture tests — and be able to draw the ring boundaries for a concrete feature.

for a principal

Should be able to weigh Onion Architecture against alternatives for a given system's actual volatility profile, recognize when the ceremony doesn't pay for itself, and design the enforcement strategy (tooling, module boundaries) that keeps the rule true at scale over years.

## The rings, from the center outward Onion Architecture, coined by **Jeffrey Palermo** in 2008, structures an application as a series of concentric rings drawn around a domain model at the very center. Moving outward, the typical rings are: - **the domain model itself** — entities, value objects, and the invariants that govern them; - **domain services** — business logic that doesn't naturally belong to a single entity, e.g., calculating whether two bookings conflict; - **application services** — orchestration code that represents a use case — 'place an order', 'transfer funds' — that coordinates domain objects, repositories, and other application services; - **and finally the outermost ring**, infrastructure and presentation, containing the database access code, the web controllers, message queue adapters, the UI, and any third-party SDKs. ## The mechanism that makes it work The mechanism that makes this work is the **Dependency Inversion Principle** applied at the architectural scale, not just the class scale. Compilation and import dependencies are only allowed to point inward: a file in an outer ring may import and depend on types defined in an inner ring, but never vice versa. Concretely, if the application service needs to save an `Order`, it does not call a SQL library or an ORM directly. Instead: 1. The inner ring declares an interface — say, `OrderRepository` — with methods like `save(order: Order)` and `findById(id): Order?`. That interface lives inside the ring that needs it. 2. The outer infrastructure ring then provides a concrete implementation, e.g., `JpaOrderRepository`, which depends on the domain's `Order` type and on the interface it implements, but the domain never imports JPA, Hibernate, or a JDBC driver. 3. At runtime, a dependency injection container wires the concrete implementation into the abstraction, so the flow of control still reaches the database, but the flow of source-level dependency has been inverted: the database module depends on the domain's contract, not the other way around. ## Different rates of change Why does this exist? Different parts of a system change at different rates and for different reasons. | What it is | Why it changes | |---|---| | Business rules — what counts as a valid order, how a shipping discount is computed | change because the business changes | | Infrastructure — which database engine you use, which web framework serves HTTP, which cloud provider hosts your queue | changes because of technical decisions, vendor migrations, performance needs, or fashion | If your business logic imports and depends on infrastructure types, then every infrastructure churn forces a review and possible rewrite of business logic, and every business change risks being entangled with technical plumbing. Onion Architecture decouples these change rates by making the volatile ring depend on the stable ring, never the reverse. A second, related benefit is **testability**: because the domain and application layers have no reference to a live database or HTTP server, you can unit-test the bulk of business logic in memory, in milliseconds, using fake or in-memory implementations of the repository interfaces — no test containers, no network calls. ## The trade-offs The trade-offs are real and worth naming honestly. - **Ceremony.** There is a genuine increase in the number of types and indirection: an interface in the domain, a mapping layer at the boundary, and a concrete adapter in infrastructure, where a simpler layered approach might have had one class. For a small CRUD app or a short-lived prototype, this ceremony can slow initial development without paying for itself, because the presumed volatility of infrastructure never materializes before the project is retired. - **Judgment calls.** Drawing the ring boundaries also requires judgment calls that are easy to get wrong — teams often misclassify what's 'domain' versus 'application' logic, causing friction and bikeshedding in code review. - **Leaky inversion.** And if the abstractions are designed poorly — e.g., a repository interface that leaks a SQL-specific concept — you get a 'leaky' inversion that provides ceremony without the actual benefit, since the domain is still implicitly coupled to infrastructure assumptions. ## How it erodes in production The most common production failure mode is **architectural erosion**: the rule is enforced by discipline and code review, not by the compiler, so over time a developer under deadline pressure imports an ORM entity annotation directly onto a domain class, or an application service calls out to a payment gateway SDK directly instead of through a port interface, because 'it's just this one time.' Months later, dependency inversion has silently collapsed in half the codebase, and nobody notices until a database migration or a framework major-version bump forces a costly untangling. Teams that take Onion Architecture seriously usually pair it with **automated enforcement** — tests that fail the build if an inner-ring file imports an outer-ring package: - `ArchUnit` in the JVM world; - `dependency-cruiser` in the JS/TS world. A well-known real-world illustration is any Spring codebase where JPA-annotated entity classes are deliberately kept separate from pure domain classes, precisely so the domain module has zero Spring or JPA imports, which is the onion rule enforced in practice.

  • Where does a repository interface live in Onion Architecture, and where does its implementation live?
    The interface (the port/contract) is declared inside an inner ring — typically alongside the domain model or in the application services ring, because that's the ring that needs to call it. The concrete implementation that talks to a specific database or ORM lives in the outermost infrastructure ring. This split is what lets the domain ring compile and be tested without any database driver on the classpath at all.
  • What tool or technique keeps a team from accidentally violating the inward-dependency rule as the codebase grows?
    Manual code review catches some violations, but it doesn't scale. Teams typically add an automated architecture test — ArchUnit on the JVM, dependency-cruiser or similar in JS/TS — that fails the build if a file in the domain or application ring imports anything from the infrastructure ring. This turns a social convention into a compiler-enforced invariant.
  • If the domain ring needs to send an email during checkout, how is that handled without the domain depending on an email library?
    The domain or application ring defines an abstraction, e.g. a NotificationSender interface with a method like sendOrderConfirmation(order). The actual implementation — using SendGrid, SMTP, or whatever — lives in infrastructure and implements that interface. The application service calls the abstraction; dependency injection supplies the concrete sender at startup.

Like an actual onion: the business rules are the core bulb that never touches outside air; each layer wrapped around it can be peeled off and replaced — a new database, a new web framework — without disturbing what's inside.

saying these in an interview costs you the question

  • Says the domain model can import the ORM's @Entity annotations 'for convenience'
  • Can't explain who owns the repository interface (thinks it lives in infrastructure)
  • Describes Onion Architecture as just 'MVC with extra folders'
  • Doesn't mention that dependencies point inward, only that there are 'layers'
  • Thinks the outer ring can be tested without also exercising the inner rings

context