Clean Architecture
Organize code in concentric rings where dependencies only point inward — business logic at the center knows nothing about databases, frameworks, or the web.
Intent & Description
🎯 Intent
Make core business logic completely independent of databases, web frameworks, UI libraries, and external services.
📋 Context
Every time you upgrade your ORM, switch databases, or migrate frameworks, it ripples through business logic that had no business knowing about those things. Tests require a running database. The core of the app is entangled with infrastructure.
💡 Solution
Organize code into four concentric rings — dependencies only flow inward:
- Entities — pure business objects and rules, zero external dependencies.
- Use Cases — application-specific workflows, orchestrate Entities.
- Interface Adapters — Controllers, Presenters, Gateways that translate between Use Cases and external world.
- Frameworks & Drivers — database, web framework, UI — all pluggable, all replaceable.
Swap Postgres for MongoDB, Express for Fastify — zero changes to business logic.
Real-world Use Case
📌 TL;DR
Business rules in the center, everything else on the outside. Dependencies point inward only. Swap any infrastructure layer without touching core logic. Boilerplate-heavy but rock-solid for long-lived systems.
Advantages
- Business logic tests run with no database, no server, no framework — blazing fast and fully isolated.
- Swap the database, ORM, or web framework without touching a single use case.
- Forces explicit boundaries that make onboarding and reasoning about the codebase much easier.
Disadvantages
- Real boilerplate cost — mappers, interfaces, and use case classes for every feature.
- Mental model shift is steep for devs used to Active Record or transaction-script patterns.
// Core Domain Entities (Inner-most ring - no external dependencies)
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
}
}
// Use Case (Interactors)
class RegisterUserUseCase {
constructor(userRepository) {
this.userRepository = userRepository;
}
async execute(userData) {
const user = new User(null, userData.name, userData.email);
return await this.userRepository.save(user);
}
}