Claude
Skills
Sign in
Back

clean-architecture

Included with Lifetime
$97 forever

This skill should be used when the user asks to "apply clean architecture", "follow SOLID principles", "add dependency injection", "separate layers", "use hexagonal/onion architecture", "domain-driven design", or needs guidance on structuring code into clean, testable, decoupled layers.

Design

What this skill does


# Clean Architecture Skill

Comprehensive guide for implementing Clean Architecture, SOLID principles, and maintainable code structures.

## When to Use This Skill

- Designing new service architecture
- Refactoring legacy code to clean architecture
- Implementing dependency injection
- Defining domain boundaries and layer separation
- Applying SOLID principles
- Reviewing architectural decisions

---

## Architecture Layers

### The Dependency Rule

**Dependencies point inward.** Inner layers must not know about outer layers.

```
┌─────────────────────────────────────────────────┐
│  External Layer (Web, CLI, GraphQL)             │
│  ┌───────────────────────────────────────────┐  │
│  │ Infrastructure (Repos, Adapters, ORM)     │  │
│  │ ┌───────────────────────────────────────┐ │  │
│  │ │ Application (Use Cases, Services)     │ │  │
│  │ │ ┌───────────────────────────────────┐ │ │  │
│  │ │ │ Domain (Entities, VOs, Services)  │ │ │  │
│  │ │ └───────────────────────────────────┘ │ │  │
│  │ └───────────────────────────────────────┘ │  │
│  └───────────────────────────────────────────┘  │
└─────────────────────────────────────────────────┘
        Dependencies point INWARD
```

### 1. Domain Layer

Business rules isolated from technical concerns:
- **Entities**: Objects with identity, business logic
- **Value Objects**: Immutable objects without identity
- **Domain Services**: Stateless operations on domain objects
- **Repository Interfaces**: Data access contracts

```typescript
// Entity with behavior
export class User {
  constructor(
    public readonly id: UserId,
    private passwordHash: PasswordHash
  ) {}

  changePassword(newPassword: Password, hasher: PasswordHasher): void {
    this.passwordHash = hasher.hash(newPassword);
  }
}

// Value Object - immutable, validated
export class Email {
  private constructor(private readonly value: string) {}

  static create(email: string): Email {
    if (!this.isValid(email)) throw new InvalidEmailError(email);
    return new Email(email.toLowerCase());
  }

  private static isValid(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }

  equals(other: Email): boolean {
    return this.value === other.value;
  }
}

// Repository interface - defines contract
export interface UserRepository {
  findById(id: UserId): Promise<User | null>;
  save(user: User): Promise<void>;
}
```

### 2. Application Layer

Orchestrates domain objects for use cases:
- **Use Cases**: Single responsibility operations
- **DTOs**: Data transfer at boundaries
- **Ports**: Interfaces for external dependencies

```typescript
export class CreateUserUseCase {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly passwordHasher: PasswordHasher
  ) {}

  async execute(input: CreateUserInput): Promise<CreateUserOutput> {
    const existing = await this.userRepository.findByEmail(
      Email.create(input.email)
    );
    if (existing) throw new EmailAlreadyExistsError();

    const user = new User(
      UserId.generate(),
      Email.create(input.email),
      this.passwordHasher.hash(input.password),
      new Date()
    );

    await this.userRepository.save(user);
    return user.toDTO();
  }
}
```

### 3. Infrastructure Layer

Implements interfaces from inner layers:
- **Repository Implementations**: Database access
- **External Adapters**: Third-party integrations
- **ORM/Query Builders**: Data persistence

```typescript
export class PostgreSQLUserRepository implements UserRepository {
  constructor(private readonly db: Database) {}

  async findById(id: UserId): Promise<User | null> {
    const row = await this.db.query('SELECT * FROM users WHERE id = $1', [id.toString()]);
    return row ? this.toDomain(row) : null;
  }

  async save(user: User): Promise<void> {
    await this.db.query(
      `INSERT INTO users (id, email, password_hash) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET email = $2`,
      [user.id.toString(), user.email.toString(), user.passwordHash]
    );
  }

  private toDomain(row: UserRow): User {
    return new User(UserId.fromString(row.id), PasswordHash.fromString(row.password_hash));
  }
}
```

### 4. Presentation Layer

Entry points to the application:
- **Controllers**: HTTP handlers
- **Resolvers**: GraphQL endpoints
- **CLI Commands**: Command-line interfaces

```typescript
export class UserController {
  constructor(private readonly createUserUseCase: CreateUserUseCase) {}

  async create(req: Request, res: Response): Promise<void> {
    try {
      const result = await this.createUserUseCase.execute(req.body);
      res.status(201).json(result);
    } catch (error) {
      if (error instanceof EmailAlreadyExistsError) {
        res.status(409).json({ error: error.message });
      }
    }
  }
}
```

---

## Project Structure

```
src/
├── domain/
│   ├── entities/          (User, Order)
│   ├── value-objects/     (Email, Money, UserId)
│   ├── services/          (PricingService)
│   ├── repositories/      (Interfaces only)
│   └── errors/
├── application/
│   ├── use-cases/         (CreateUser, UpdateOrder)
│   ├── services/          (NotificationService)
│   ├── ports/             (EmailPort, PaymentPort)
│   └── dto/
├── infrastructure/
│   ├── repositories/      (PostgreSQL, MongoDB implementations)
│   ├── adapters/          (SendGrid, Stripe)
│   ├── orm/
│   └── config/
├── presentation/
│   ├── http/              (Controllers, Routes, Middleware)
│   ├── graphql/           (Resolvers)
│   └── cli/               (Commands)
├── shared/                (Utilities, Kernel helpers)
└── container/             (Dependency Injection setup)
```

---

## Dependency Injection

```typescript
// src/container/container.ts
import { Container } from 'inversify';

const container = new Container();

// Bind implementations to interfaces
container.bind<UserRepository>(TYPES.UserRepository)
  .to(PostgreSQLUserRepository)
  .inSingletonScope();

container.bind<CreateUserUseCase>(TYPES.CreateUserUseCase)
  .to(CreateUserUseCase)
  .inTransientScope();

container.bind<UserController>(TYPES.UserController)
  .to(UserController)
  .inTransientScope();

export { container };
```

---

## SOLID Principles

### Single Responsibility
Each layer has one reason to change:
- Domain: Business rules
- Application: Use case coordination
- Infrastructure: Technical implementations
- Presentation: User interface

### Open/Closed
Add features by creating new use cases, not modifying existing:
```typescript
export class UpdateUserUseCase { /* ... */ }
```

### Liskov Substitution
Repository implementations are fully interchangeable:
```typescript
const repo: UserRepository = new PostgreSQLUserRepository(db);
const repo: UserRepository = new MongoUserRepository(client);
// Both satisfy the contract
```

### Interface Segregation
Use focused interfaces, not fat ones:
```typescript
// Good: Segregated
interface UserCreator { create(data): User; }
interface UserDeleter { delete(id): void; }

// Bad: Fat interface
interface UserService {
  create(): User;
  update(): User;
  delete(): void;
  sendEmail(): void;
  generateReport(): Report;
}
```

### Dependency Inversion
Depend on abstractions, not implementations:
```typescript
// Application defines the port
export interface EmailPort {
  send(to: string, subject: string, body: string): Promise<void>;
}

// Infrastructure implements
export class SendGridAdapter implements EmailPort {
  async send(to: string, subject: string, body: string): Promise<void> {
    await this.sendgrid.send({ to, subject, text: body });
  }
}

// Use cases depend on port
export class CreateUserUseCase {
  constructor(private readonly emailPort: EmailPort) {}
}
```

---

## Testing

```typescript
// Unit: Domain logic without infrastructure
describe('User', () => {
  it('should change password', () => {
    const hasher = new BCryptHasher();
    const user = new User(UserId.generate(), hasher.hash('oldpass'));
    user.changePassw

Related in Design