skip to content

Clean Architecture

Concentric rings — entities, use cases, interface adapters, frameworks and drivers — with the Dependency Rule saying source dependencies only point inward. You will also cover screaming architecture and how it compares with hexagonal and onion, which are close cousins.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

In Clean Architecture, the 'Dependency Rule' says source code dependencies can only point inward. If a UseCase class needs to save data to a PostgreSQL database, why shouldn't that UseCase class directly import a JDBC or ORM library?

level: juniorimportance: must knowfreq 75%

answer

  1. arrows point inward only
  2. stable core, volatile edge
  3. interface owned by inner ring
  4. DIP flips the compile dependency
  5. swap tech without touching use cases

basics

~10 s

Inner circles (business rules) must never depend on outer circles (frameworks/DB). The use case defines an interface it needs; a database-specific class outside implements it. This keeps business logic free of database code.

solid answer

~40 s

The Dependency Rule states that source-code dependencies may only point inward, toward higher-level policy. Entities and use cases are the innermost, most stable layer; frameworks, DB drivers, and UI are outermost and most volatile. If a UseCase directly imported JDBC, the business-rule file would depend on a low-level, changeable technology, coupling stable code to unstable code and violating the Dependency Inversion Principle. Instead, the use case declares an interface (e.g. OrderRepository) that it owns; a Gateway/Repository implementation in the infrastructure layer implements it and is wired in at the outer edge, often via a DI container. This lets business logic be compiled, tested, and reasoned about without any database, framework, or UI present at all, and lets you swap Postgres for DynamoDB without touching a single use case.

go deeper

for a junior

Can state that dependencies point inward and give one plausible reason (easier testing, less coupling to the database).

for a middle

Can explain the actual mechanism: the inner ring declares an interface, the outer ring implements it, and a DI container wires the concrete class in at startup.

for a senior

Can discuss how to enforce the rule with tooling (ArchUnit, module boundaries) and can spot violations like framework annotations leaking into use-case classes.

for a principal

Can weigh when the discipline is worth its overhead versus when a simpler, less-inverted structure is the pragmatic choice for the project's actual risk profile.

## The four concentric rings Clean Architecture, popularized by Robert C. Martin ("Uncle Bob") in his 2012 blog post and later the 2017 book Clean Architecture, organizes a system into concentric rings and states one governing rule: the **Dependency Rule**. Source code dependencies - the import or use statements in your code - may only point inward, from outer rings toward inner rings, never the reverse. - **Entities** - the innermost ring holds the most general, most stable business rules and data structures, the kind of logic that would exist even if you changed how the whole application is delivered. - **Use Cases** (sometimes called **Interactors**) - the next ring out holds application-specific business rules that orchestrate entities to accomplish a particular task, like 'place an order' or 'cancel a subscription.' - **Interface Adapters** - outside that sits controllers, presenters, and gateways that translate data between the use-case-friendly format and whatever format the next ring needs. - **Frameworks & Drivers** - the outermost ring is the web framework, the database driver, the UI toolkit, the ORM - the most volatile, most replaceable, most detail-laden code in the system. ## The mechanism: Dependency Inversion The mechanism for keeping dependencies pointing inward when the natural runtime data flow often needs to go the other way (a use case needs to read/write a database, an outer-ring concern) is the **Dependency Inversion Principle**. 1. The inner ring defines an abstraction - an interface - that describes only what it needs, in its own vocabulary, and that interface physically lives inside the inner ring's source tree or package. 2. The outer ring then implements that interface. 3. So in a Kotlin backend, `OrderRepository` (an interface with methods like `findById`, `save`) lives in the use-case package, and `JpaOrderRepository`, which knows about Hibernate, SQL, and connection pools, lives in the infrastructure package and implements `OrderRepository`. The compiled dependency arrow (infrastructure -> use case) now points inward even though the runtime call still flows from use case to infrastructure implementation at execution time. This is the crucial distinction the rule turns on: it governs **source-level dependency direction**, not the direction of a runtime function call. ## Why does this exist? The core problem it solves is protecting the parts of the system that encode genuine, hard-won business knowledge - what 'placing an order' actually means for this company - from churn caused by parts of the system that are essentially interchangeable technology choices. Databases get migrated, web frameworks get upgraded or replaced, UI toolkits go in and out of fashion, but the rule 'an order cannot ship without a valid payment' rarely changes for reasons related to any of that. By making the use case ignorant of JPA, Spring, or the specific SQL dialect in use: - you can unit-test the use case with a fake in-memory implementation of `OrderRepository` in milliseconds, with zero database, zero HTTP server, and zero framework bootstrap; - you can also swap Postgres for DynamoDB, or REST for gRPC, by writing new adapters, without touching a single use-case class - a genuine, measurable reduction in blast radius for infrastructure changes. ## The trade-off The trade-off is that this indirection is not free. Every one of these boundaries typically means: - an extra **interface**; - at least one extra **implementation class**; - often a **DTO or mapping layer** to convert between the use case's domain model and whatever shape the database, JSON API, or third-party SDK expects. For a small CRUD service with two entities and a handful of endpoints, this ceremony can genuinely cost more in code volume and onboarding time than it saves, and teams frequently over-apply the pattern to systems that will never actually swap a database or a UI, paying the tax without ever collecting the benefit. ## Failure modes 1. The failure mode that shows up most often in production codebases is what's sometimes called an **anemic or 'pass-through' use case**: an interactor class that does nothing but immediately delegate to a repository call, adding a layer of indirection with zero actual business logic in it - a sign the abstraction is being applied mechanically rather than where it earns its keep. 2. A second common failure is **leakage in the other direction**: JPA `@Entity` annotations, Spring `@Component` annotations, or Jackson `@JsonProperty` annotations end up directly on domain/use-case classes 'because it's convenient,' which silently violates the Dependency Rule even though the code still compiles - the use-case package now has a hard source dependency on `javax.persistence` or `com.fasterxml.jackson`, an outer-ring framework, and can no longer be unit tested or reused without dragging that framework along. ## A worked example A concrete example: a payment-processing use case class `ProcessRefundInteractor` depends only on a `RefundGateway` interface it declares itself. - In production, `StripeRefundGateway` implements it by calling the Stripe SDK. - In tests, `FakeRefundGateway` implements it with an in-memory list. Both satisfy the same interface, so the interactor's logic - validating the refund amount against the original charge, checking refund eligibility windows - is tested without a network call, and the team was later able to add a second payment provider adapter without editing the interactor at all.

  • How would you enforce the Dependency Rule automatically in CI, rather than relying on code review to catch violations?
    Use an architecture-testing tool (e.g. ArchUnit for JVM, dependency-cruiser for JS/TS) that scans import statements and fails the build if a package in the use-case or entity layer imports anything from an infrastructure or framework package. This turns a convention into a hard, automated constraint that can't silently regress.
  • At runtime, the use case still calls the repository implementation to fetch data. Doesn't that mean the dependency really does point outward?
    The runtime call and the source dependency are different things. The use case calls a method on an interface it owns; the concrete implementation is looked up (usually via a DI container) and injected at startup. The compiled 'import'/'implements' relationship still points from the outer implementation class inward to the interface, satisfying the rule even though control flows outward at runtime.
  • Can Entities depend on Use Cases in this model?
    No - Entities are the innermost, most general ring and Use Cases sit one ring further out, so dependencies must point from Use Cases toward Entities, never the reverse. An entity that imported a use-case class would be depending on more volatile, application-specific logic, inverting the intended stability gradient.

Like a hospital's medical protocol for treating a heart attack: the protocol just says 'record the patient outcome' - it doesn't care whether the hospital's records system is Epic or Cerner. Whichever system is plugged in has to conform to the protocol's needs, not the other way around.

saying these in an interview costs you the question

  • suggests importing a database/ORM class directly into a use case 'for convenience'
  • describes the Dependency Rule as being about runtime call order rather than compile-time import direction
  • can't name what mechanism (DIP/interface ownership) lets the runtime call still satisfy an inward-pointing dependency
  • thinks Entities can depend on Use Cases
  • claims the rule means outer-ring code (frameworks, DB) can never be used at all

context

open as a page

Clean Architecture draws four concentric rings - Entities, Use Cases, Interface Adapters, and Frameworks & Drivers. For a typical web application, what kind of code lives in each of these four rings, and how does data get from an outer ring's format into a form the innermost ring can use?

level: middleimportance: must knowfreq 80%

basics

~20 s

Entities hold core business rules; Use Cases run application-specific workflows; Interface Adapters translate between the app and the outside world (controllers, presenters); Frameworks/Drivers are the actual web server, DB, UI. Data crosses rings as plain data structures, converted at each boundary.

open as a page

Clean Architecture adds interfaces, DTOs, and mapping code at every ring boundary. What concrete trade-offs does that indirection buy you, and in what kind of project would a senior engineer recommend against applying the full four-ring structure?

level: seniorimportance: must knowfreq 60%

basics

~20 s

You gain testability and the ability to swap frameworks/DBs later, but you pay in more files, more boilerplate, and slower ramp-up. For a small short-lived CRUD app that will never swap its DB or framework, skip the full structure.

open as a page

Robert C. Martin's phrase 'screaming architecture' argues that a codebase's top-level structure should scream something specific about the system. What should it scream, and what's an example of an architecture that screams the wrong thing?

level: middleimportance: should knowfreq 45%

basics

~10 s

The top-level folders should announce what the app DOES (its use cases/domain), like 'Shipping' or 'PatientIntake' - not what framework it's built with, like 'controllers/models/views.'

open as a page

Clean Architecture's Interface Adapters ring contains Controllers and Presenters that sit between Use Cases and the outside world. Concretely, what data crosses a use-case boundary in each direction, and why shouldn't a use case just accept and return its own Entity objects directly to a REST controller?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A controller converts an incoming request into a simple input object the use case understands; the use case returns a simple output object; a presenter turns that into a view model/JSON. Passing entities straight out would leak internal business objects (and their behavior) into the web layer.

open as a page

Clean Architecture, Hexagonal Architecture, and Onion Architecture are frequently cited together as 'the same idea with different diagrams.' At the level of what each one actually prescribes, what is Clean Architecture's specific structure, and what's genuinely different about it versus just being a relabeling of the other two?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

All three push business logic to the center and keep dependencies pointing inward. Clean Architecture specifically names four rings (Entities, Use Cases, Interface Adapters, Frameworks/Drivers) and pairs that with the Dependency Rule and 'screaming architecture' - a bit more prescriptive about layer count and naming than the other two.

open as a page