The Trap of Framework Coupling
When database ORM models and HTTP controller routing annotations leak directly into business calculation logic, upgrading an HTTP framework or swapping database providers triggers massive codebase rewrites. Clean Architecture separates pure business rules from volatile external dependencies.
The Dependency Rule
Source code dependencies must point inward toward high-level domain policies:
- Domain Entities: Business rules, value objects, domain events (Zero framework imports).
- Use Cases / Application Services: Orchestrates flows and defines repository interfaces.
- Adapters / Infrastructure: PostgreSQL repositories, Stripe payment clients, HTTP routing handlers.
// Domain interface (inward)
export interface UserRepository {
findById(id: string): Promise;
save(user: User): Promise;
}
// Application use case
export class RegisterUserUseCase {
constructor(private userRepo: UserRepository) {}
async execute(dto: RegisterUserDto): Promise {
const existing = await this.userRepo.findById(dto.id);
if (existing) throw new Error('User exists');
const user = new User(dto.id, dto.email);
await this.userRepo.save(user);
return user;
}
}