Clean Architecture
Clean Architecture (Uncle Bob) codifies concentric layers: Entities → Use Cases → Interface Adapters → Frameworks.
Introduction
Clean Architecture (Uncle Bob) codifies concentric layers: Entities → Use Cases → Interface Adapters → Frameworks. The dependency rule: source code dependencies point inward only. Frameworks and DB are plugins at the outer edge — business rules at the center survive technology churn.
The story
Teams at Uber and major banks converged on Clean/Hexagonal/Onion independently. Adopters report longer codebase lifespans — framework upgrades become adapter swaps, not domain rewrites.
The business problem
Framework-as-architecture makes upgrades bet-the-company projects:
- Upgrade paralysis: Spring/Rails version jump touches every file.
- Logic scatter: same rule in controller, service, and repository.
- Untestable use cases: business flow requires HTTP + DB to test.
- Onboarding chaos: no agreement where new code belongs.
The problem teams faced
Clean Architecture fits when:
- System expected to live 5+ years with framework/DB changes.
- Use cases must be testable independent of delivery mechanism.
- Multiple interfaces (REST, GraphQL, gRPC) share business rules.
- Team needs explicit dependency rule beyond informal layers.
Understanding the topic
Intent: Concentric layers with inward-only dependencies.
- Entities — enterprise business rules (Order, Money).
- Use Cases — application-specific rules (PlaceOrder).
- Interface Adapters — controllers, presenters, gateways.
- Frameworks & Drivers — DB, web framework, external APIs.
Internal architecture
Clean Architecture rings:
[ Web · DB · Queue ] ← Frameworks (outer)[ Controllers · GW ] ← Interface Adapters[ Use Cases ] ← Application[ Entities ] ← Domain (inner)PlaceOrderUseCase → depends on → OrderRepository (interface)PostgresOrderRepository → implements → OrderRepositoryOrderController → calls → PlaceOrderUseCase
Visual explanation
Three diagrams cover dependency rule, layer ring, and use case isolation:
Informative example
Before / after — framework-coupled to Clean use case:
// ❌ Use case imports Express and Prismaexport async function placeOrder(req: Request, res: Response) {const order = await prisma.order.create({ data: req.body })res.json(order)}// ✅ Use case — zero framework importsclass PlaceOrderUseCase {constructor(private orders: OrderRepository,private payments: PaymentGateway,) {}async execute(cmd: PlaceOrderCommand): Promise<OrderId> {const order = Order.create(cmd)await this.payments.charge(order.total)await this.orders.save(order)return order.id}}// Adapter — Express only hererouter.post("/orders", async (req, res) => {const id = await placeOrderUseCase.execute(mapReq(req))res.json({ id })})
Execution workflow
Entities first
Model Order, Money with business invariants.
Real-world use
Uncle Bob's Clean Architecture book; converges with Hexagonal and Onion; NestJS clean modules; many bank and fintech codebases.
Production case study
Framework migration at scale:
- Scenario: Bank codebase tied to aging application server.
- Decision: Clean layers — use cases extracted with port interfaces.
- Outcome: New delivery mechanism (gRPC) added as adapter; domain untouched.
Trade-offs
- Pro: framework longevity; clear dependency rule; testable core.
- Con: significant upfront structure; overkill for small apps.
- Con: team discipline required — rings degrade without CI enforcement.
Decision framework
- Use for long-lived systems with expected tech churn.
- Use when use case tests are architectural requirement.
- Avoid for prototypes — start layered, evolve toward Clean when pain appears.
Best practices
- One use case class per user story — PlaceOrder, CancelOrder.
- Entities hold invariants — not just data transfer.
- Enforce dependency rule mechanically in CI.
Anti-patterns to avoid
- Clean folder structure without dependency inversion — cargo cult.
- Use cases that import ORM entities from outer ring.
- 100 use case classes with zero domain logic — anemic.
Common mistakes
- Mapping overhead between layers — use mappers at adapter boundary only.
- Team disagreement on ring placement — document in ADR.
Debugging tips
- If entity tests pass but API fails — bug in adapter mapping layer.
Optimization strategies
- Don't build all rings day one — grow inward as rules accumulate.
Common misconceptions
- Clean ≠ many layers mandatory on day 1. Dependency rule is the point; rings can start minimal.
- Same forces as Hexagonal — different diagram, same inversion.
Advanced interview questions
Interview Prep
Practice concise answers, then expand each card for the explanation.
1IntermediateQuestionClean Architecture dependency rule?+
Answer
Follow-up
2AdvancedQuestionEntities vs Use Cases?+
Answer
Follow-up
3IntermediateQuestionClean vs Hexagonal?+
Answer
Follow-up
Summary
You can explain the dependency rule, place use cases at the center, and test without frameworks. Tell the framework migration survival story — if you can do that, you own Clean Architecture.