| name | applying-clean-architecture |
| description | Enforces Clean Architecture principles in TypeScript. Use to ensure layer isolation, dependency inversion, and business logic purity in Node.js applications. |
| metadata | {"sources":["Clean Architecture: A Craftsman's Guide to Software Structure and Design (Robert C. Martin)","Domain-Driven Design (Eric Evans)","Working Effectively with Legacy Code (Michael Feathers)"]} |
Clean Architecture - TypeScript Best Practices
This skill provides rules and guidance for maintaining a scalable, testable, and maintainable architecture based on the principles of Clean Architecture and Domain-Driven Design (DDD).
Core Principles
- Independence of Frameworks: The architecture does not depend on the existence of some library of feature-laden software.
- Testability: The business rules can be tested without the UI, Database, Web Server, or any other external element.
- Independence of UI: The UI can change easily, without changing the rest of the system.
- Independence of Database: You can swap out SQL for NoSQL, or change your ORM without breaking business logic.
- Independence of any external agency: Business rules simply don’t know anything about the outside world.
1. critical-layer-isolation
Why it matters: Importing infrastructure (Express, Sequelize) into the domain layer makes it impossible to test business logic in isolation and creates "leaky abstractions."
❌ Incorrect:
import { Request } from 'express';
import { Model, DataTypes } from 'sequelize';
export class User extends Model {
}
✅ Correct:
export interface User {
id: string;
email: string;
name: string;
}
Impact: CRITICAL - Ensures the core can survive technology changes.
2. high-dependency-inversion
Why it matters: High-level policy (use cases) should not depend on low-level detail (DB implementations). They should depend on abstractions.
❌ Incorrect:
import { UserRepository } from '../../infrastructure/UserRepository';
export class RegisterUser {
private repo = new UserRepository();
}
✅ Correct:
export interface IUserRepository {
save(user: User): Promise<void>;
}
import { IUserRepository } from '../../domain/repositories/IUserRepository';
export class RegisterUser {
constructor(private userRepo: IUserRepository) {}
}
Impact: HIGH - Decouples business logic from storage details.
3. high-use-case-orchestration
Why it matters: Business logic should be centralized in Use Cases (Interactors) to ensure it is discoverable and reusable.
Detection:
- Look for business logic inside Express controllers.
- Check for direct database calls in the UI/API layer.
✅ Correct Pattern:
export class ProcessPayment {
constructor(
private paymentService: IPaymentService,
private orderRepo: IOrderRepo
) {}
async execute(orderId: string) {
const order = await this.orderRepo.find(orderId);
await this.paymentService.process(order);
order.markAsPaid();
await this.orderRepo.save(order);
}
}
Impact: HIGH - Improves discoverability and reduces "Fat Controllers."
4. high-screaming-architecture
Why it matters: The directory structure should reflect the business domain (The "What"), not the tools or frameworks (The "How").
❌ Incorrect:
src/
controllers/
models/
views/
routes/
✅ Correct:
src/
orders/
entities/
use-cases/
gateways/
billing/
entities/
use-cases/
Impact: HIGH - Makes the system intent obvious from the first glance.
5. high-humble-delivery-mechanism
Why it matters: The Web, UI, and Frameworks are implementation details. Use the Humble Object pattern to separate untestable framework code from testable business logic.
Detection:
- Look for business logic intertwined with
req, res, document, or specific library components.
✅ Correct:
- Use Case (Smart): Contains all the logic.
- Controller/Presenter (Humble): Simply passes data from the framework to the use case and transforms the output.
Impact: HIGH - Ensures maximum testability of core logic.
6. high-stable-dependency-direction
Why it matters: Dependencies must always point inwards towards the stable core (Entities/Use Cases). Stable components should not depend on volatile ones.
The Rule:
- Entities ← Use Cases ← Adapters ← Frameworks
Impact: HIGH - Prevents "Detail" changes from breaking "Core" business rules.
7. medium-dto-isolation
Why it matters: Domain entities should not be exposed directly to the outside world (API) as they may contain sensitive data or business logic.
❌ Incorrect:
async function getUser(req: Request, res: Response) {
const user = await userRepo.findById(req.params.id);
res.json(user);
}
✅ Correct:
export interface UserPresenter {
id: string;
displayName: string;
}
Impact: MEDIUM - Protects domain integrity and improves security.
References