HT
How Things Work
System Online

Clean Architecture

How layering and the dependency rule keep business logic independent of frameworks, databases, and UI β€” so the core stays stable, testable, and swappable.

How It Works

Clean Architecture (and its cousins Hexagonal / Ports & Adapters and Onion Architecture) organizes code into concentric layers with one rule: dependencies point inward, toward the stable business core. Frameworks, databases, and UI become replaceable plugins at the edge. The result is software whose most important code β€” the business rules β€” doesn't depend on, and can't be broken by, the volatile details around it.

1
Concentric Layers

Code is organized in rings: Entities (core domain) at the center, then Use Cases (application rules), then Interface Adapters (controllers, presenters, repository implementations), then Frameworks & Drivers (web, DB, UI) on the outside. The center is the most stable; the edges are the most volatile.

2
The Dependency Rule

Source-code dependencies point only inward. Outer layers may depend on inner ones, never the reverse. Entities know nothing about use cases; use cases know nothing about the web or database. This single rule is what makes the architecture 'clean'.

3
Dependency Inversion at the Boundaries

But a use case still needs to save data, which lives in an outer layer. The trick: the use case defines an interface (a 'port') and the outer layer implements it (an 'adapter'). At runtime the concrete adapter is injected β€” so control flows outward while dependencies still point inward.

4
Frameworks Are Details

The web framework, ORM, database, and UI are plugins at the edge, not the foundation. Because the core depends only on abstractions, you can swap Express for Fastify, Postgres for Mongo, or REST for gRPC without touching a single business rule β€” and you can test the core with zero infrastructure.

Key Concepts

➑️Dependency Rule

Source dependencies point only inward, toward higher-level policy. The non-negotiable core of clean architecture.

πŸ’ŽEntities

Enterprise-wide business objects and rules. The innermost, most stable layer β€” pure domain, no framework.

βš™οΈUse Cases

Application-specific rules that orchestrate entities to fulfill one user action. Depend only on entities.

πŸ”ŒPorts & Adapters

Interfaces defined by the core (ports) and implemented by outer layers (adapters). Aka Hexagonal Architecture.

πŸ”„Interface Adapters

Controllers, presenters, and repository implementations that translate between the outside world and use cases.

🌱Composition Root

The single outer place where concrete implementations are wired into the abstractions (the DI container).

Ports & adapters
tsx
1// The dependency rule via ports & adapters.
2// Use case depends on an interface it OWNS β€” not on the database.
3
4// --- core (use cases layer) ---
5interface OrderRepository { // a "port" defined inward
6 save(order: Order): Promise<void>;
7}
8
9class PlaceOrder { // application business rule
10 constructor(private repo: OrderRepository) {}
11 async execute(cmd: PlaceOrderCommand) {
12 const order = Order.create(cmd.items); // entity enforces invariants
13 await this.repo.save(order);
14 return order.id;
15 }
16}
17
18// --- outer (interface adapters layer) ---
19class SqlOrderRepository implements OrderRepository { /* Postgres */ }
20
21// --- composition root (frameworks layer) ---
22const useCase = new PlaceOrder(new SqlOrderRepository());
23// Swap to MongoOrderRepository and PlaceOrder never changes.
πŸ’‘
Why This Matters

Clean Architecture is the dominant way large .NET and enterprise systems are structured, and it's a frequent interview and design-review topic. Its real payoff is changeability: business rules you can unit-test without infrastructure, frameworks you can swap without a rewrite, and a codebase whose structure screams its purpose rather than its framework.

Common Pitfalls

⚠Over-layering small projects β€” the ceremony of 4 layers and mappers can dwarf the actual logic.
⚠Leaking framework or ORM types into the core (e.g. EF entities as domain models) β€” breaks independence.
⚠Letting an inner layer reference an outer one 'just this once' β€” one violation erodes the whole dependency rule.
⚠Anemic entities with all logic in services β€” the domain core ends up empty.
⚠Mapping fatigue: so many DTO↔entity conversions that developers route around the architecture.
Real-World Use Cases

1Business Logic Trapped in Controllers

Scenario

A 'quick' API put all the order logic directly in the Express route handlers, with raw SQL inline. Now the rules can only run inside an HTTP request, can't be reused by a background job, and unit tests need a running web server and database.

Problem

There's no separation between delivery (HTTP), application rules, and data access. The business logic is coupled to the framework and the database, so it isn't reusable or testable in isolation β€” every change risks the web layer.

Solution

Extract use-case classes (PlaceOrder, CancelOrder) that depend on repository interfaces. Controllers become thin: parse input, call the use case, format output. The same use cases now run from a queue worker or CLI, and unit tests exercise them with in-memory repositories β€” no HTTP, no DB.

πŸ’‘

Takeaway: Keep business rules independent of the delivery mechanism. Thin controllers calling framework-agnostic use cases make logic reusable across HTTP/jobs/CLI and trivially testable.

2Swapping the Database Without a Rewrite

Scenario

A product needs to migrate from a SQL database to a document store for one bounded context, but the team fears it means touching business logic everywhere.

Problem

In a typical layered app where services call the ORM directly, the persistence technology leaks into the business code, so a storage change ripples through the whole codebase.

Solution

Because the core only depends on repository interfaces (ports), they write a new adapter (MongoOrderRepository) implementing the same interface and change one line at the composition root. The use cases and entities are untouched β€” the database really was just a detail.

πŸ’‘

Takeaway: When the core depends on abstractions it owns, infrastructure becomes swappable. Clean architecture's payoff is exactly this: changing a 'detail' like the database doesn't change the business rules.

Join the discussion

Comments

?

Loading comments...

Cookie preferences

We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.