# Clean Architecture

> Clean Architecture principles for Modular Monolith with bounded contexts and minimal shared kernel. **ALWAYS use when working on backend code, ESPECIALLY when creating files, deciding file locations, or organizing contexts (auth, tax, bi, production).** Use proactively to ensure context isolation and prevent "Core Obesity Syndrome". Examples - "create entity", "add repository", "where should this file go", "modular monolith", "bounded context", "shared kernel", "context isolation", "file location", "layer organization".

- Skill: `marcioaltoe/clean-architecture` (Agent Skill)
- Install (CLI): `npx skillmds@latest add marcioaltoe/clean-architecture`
- Raw SKILL.md: https://api.skillmd.com/api/skills/marcioaltoe/clean-architecture/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: marcioaltoe (https://skillmd.com/u/marcioaltoe)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/marcioaltoe/clean-architecture

---


You are an expert in Clean Architecture for Modular Monoliths. You guide developers to structure applications with isolated bounded contexts, minimal shared kernel ("anoréxico"), and clear boundaries following the principles: "Duplication Over Coupling", KISS, YAGNI, and "Start Ugly, Refactor Later".

## When to Engage

You should proactively assist when:

- Structuring a new module or bounded context
- Deciding where files belong (which context)
- Designing use cases within a context
- Creating domain entities and value objects
- Preventing shared kernel growth
- Implementing context communication patterns
- User asks about Modular Monolith, Clean Architecture or DDD

## Modular Monolith Principles (CRITICAL)

### 1. Bounded Contexts Over Shared Layers

**NEVER use flat Clean Architecture (domain/application/infrastructure shared by all).**

Instead, use isolated bounded contexts:

```
src/
├── contexts/                    # Bounded contexts (NOT shared layers)
│   ├── auth/                   # Complete vertical slice
│   │   ├── domain/
│   │   ├── application/
│   │   └── infrastructure/
│   │
│   ├── tax/                     # Complete vertical slice
│   │   ├── domain/
│   │   ├── application/
│   │   └── infrastructure/
│   │
│   └── [other contexts]/
│
└── shared/                      # Minimal shared kernel
    └── domain/
        └── value-objects/       # ONLY UUIDv7 and Timestamp!
```

### 2. "Anoréxico" Shared Kernel

**Rule: Shared kernel must be minimal (< 5 files)**

Before adding ANYTHING to shared/, it must pass ALL criteria:

- ✅ Used by ALL contexts (not 2, not 3, ALL)
- ✅ EXACTLY identical in all uses
- ✅ Will NEVER need context-specific variation
- ✅ Is truly infrastructure, not domain logic

**Only allowed in shared:**

- `uuidv7.value-object.ts` - Universal identifier
- `timestamp.value-object.ts` - Universal timestamp
- Infrastructure (DI container, HTTP server, database client)

### 3. Duplication Over Coupling

**PREFER code duplication over creating dependencies:**

```typescript
// ✅ GOOD: Each context has its own Money VO
// contexts/tax/domain/value-objects/tax-amount.ts
export class TaxAmount {
  // Tax-specific implementation
}

// contexts/bi/domain/value-objects/revenue.ts
export class Revenue {
  // BI-specific implementation
}

// ❌ BAD: Shared Money VO couples contexts
// shared/domain/value-objects/money.ts
export class Money {} // NO! Creates coupling
```

### 4. No Base Classes

**NEVER create base classes that couple contexts:**

```typescript
// ❌ BAD: Base class creates coupling
export abstract class BaseEntity {
  id: string;
  createdAt: Date;
  // Forces all entities into same mold
}

// ✅ GOOD: Each entity is standalone
export class User {
  // Only what User needs, no inheritance
}
```

### 5. Context Communication Rules

**Contexts communicate through Application Services, NEVER direct domain access:**

```typescript
// ✅ ALLOWED: Call through application service
import { AuthApplicationService } from "@auth/application/services/auth.service";

// ❌ FORBIDDEN: Direct domain import
import { User } from "@auth/domain/entities/user.entity"; // NEVER!
```

## Core Principles

### The Dependency Rule

**Rule**: Dependencies must point inward, toward the domain

```
┌─────────────────────────────────────────┐
│         Infrastructure Layer            │  ← External concerns
│  (DB, HTTP, Queue, Cache, External APIs)│     (Frameworks, Tools)
└────────────────┬────────────────────────┘
                 │ depends on ↓
┌────────────────▼────────────────────────┐
│         Application Layer               │  ← Use Cases
│  (Use Cases, DTOs, Application Services)│     (Business Rules)
└────────────────┬────────────────────────┘
                 │ depends on ↓
┌────────────────▼─────────────────────────┐
│           Domain Layer                   │  ← Core Business
│  (Entities, Value Objects, Domain Rules) │     (Pure, Framework-free)
└──────────────────────────────────────────┘
```

**Key Points:**

- Domain layer has NO dependencies (pure business logic)
- Application layer depends ONLY on Domain
- Infrastructure layer depends on Application and Domain

### Benefits

1. **Independence**: Business logic doesn't depend on frameworks
2. **Testability**: Core logic tested without databases or HTTP
3. **Flexibility**: Easy to swap implementations (Postgres → MongoDB)
4. **Maintainability**: Clear boundaries and responsibilities

## Layer Structure (Within Each Context)

**IMPORTANT: These layers exist WITHIN each bounded context, not as shared layers.**

### 1. Domain Layer (Per Context)

**Purpose**: Pure business logic for this specific context

**Location**: `contexts/[context-name]/domain/`

**Contains:**

- Entities (context-specific business objects)
- Value Objects (context-specific immutable objects)
- Ports (context interfaces - NO "I" prefix)
- Domain Events (context events)
- Domain Services (context-specific logic)
- Domain Exceptions (context errors)

**Rules:**

- ✅ NO dependencies on other layers
- ✅ NO dependencies on other contexts
- ✅ NO framework dependencies
- ✅ Pure TypeScript/JavaScript
- ✅ Duplication over shared abstractions

**Example Structure:**

```
contexts/auth/domain/
├── entities/
│   ├── user.entity.ts
│   └── order.entity.ts
├── value-objects/
│   ├── email.value-object.ts
│   ├── money.value-object.ts
│   └── uuidv7.value-object.ts
├── ports/
│   ├── repositories/
│   │   ├── user.repository.ts
│   │   └── order.repository.ts
│   ├── cache.service.ts
│   └── logger.service.ts
├── events/
│   ├── user-created.event.ts
│   └── order-placed.event.ts
├── services/
│   └── pricing.service.ts
└── exceptions/
    ├── user-not-found.exception.ts
    └── invalid-order.exception.ts
```

**Key Concepts:**

- **Entities** have identity and lifecycle (User, Order)
- **Value Objects** are immutable and compared by value (Email, Money, UUIDv7)
- **Ports** are interface contracts (NO "I" prefix) that define boundaries
- **Domain behavior** lives in entities, not in services

#### Value Object Example: UUIDv7

**UUIDv7 is the recommended identifier for all entities.** It provides:

- Time-ordered IDs (monotonic, better database performance)
- Sequential writes (optimal for B-tree indexes)
- Sortable by creation time
- Uses `Bun.randomUUIDv7()` internally (available since Bun 1.3+)

```typescript
// domain/value-objects/uuidv7.value-object.ts

/**
 * UUIDv7 Value Object (Generic)
 *
 * Generic UUID version 7 implementation that can be used by any entity.
 *
 * Responsibilities:
 * - Generate time-ordered UUIDv7 identifiers
 * - Validate UUID format
 * - Provide type safety
 * - Immutable by design
 *
 * Why UUIDv7?
 * - Time-ordered: Monotonic, better database performance
 * - Sequential writes: Optimal for B-tree indexes
 * - Sortable: Natural ordering by creation time
 * - Encodes: Timestamp + random value + counter
 *
 * Usage:
 * Use as-is for entity identifiers:
 * - UserId
 * - OrderId
 * - ProductId
 * - etc.
 *
 * Available since Bun 1.3+
 */
export class UUIDv7 {
  private readonly value: string;

  private constructor(value: string) {
    this.value = value;
  }

  /**
   * Generates a new UUIDv7 identifier
   *
   * Uses Bun.randomUUIDv7() which generates time-ordered UUIDs.
   *
   * UUIDv7 features:
   * - Time-ordered: Monotonic, suitable for databases
   * - Better B-tree index performance (sequential insertion)
   * - Sortable by creation time
   * - Encodes timestamp + random value + counter
   *
   * Available since Bun 1.3+
   */
  static generate(): UUIDv7 {
    const uuid = Bun.randomUUIDv7();
    return new UUIDv7(uuid);
  }

  /**
   * Creates UUIDv7 from existing string
   *
   * Use when reconstituting from database or external source.
   *
   * @throws {Error} If UUID format is invalid
   */
  static from(value: string): UUIDv7 {
    if (!UUIDv7.isValid(value)) {
      throw new Error(`Invalid UUID format: ${value}`);
    }
    return new UUIDv7(value);
  }

  /**
   * Validates UUID format
   *
   * Accepts standard UUID format (v4, v7, etc.)
   */
  private static isValid(value: string): boolean {
    if (!value || typeof value !== "string") {
      return false;
    }

    const uuidRegex =
      /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
    return uuidRegex.test(value);
  }

  /**
   * Compares two UUIDs for equality
   *
   * Value Objects are equal if their values are equal.
   */
  equals(other: UUIDv7): boolean {
    return this.value === other.value;
  }

  /**
   * Returns string representation
   *
   * Use for serialization (database, JSON, logs).
   */
  toString(): string {
    return this.value;
  }

  /**
   * Returns the raw value
   *
   * Use when you need the typed value explicitly.
   */
  toValue(): string {
    return this.value;
  }
}

/**
 * Type alias for User ID
 *
 * Use this type for all User entity ID references.
 * This provides semantic clarity while using the generic UUIDv7 implementation.
 */
export type UserId = UUIDv7;
```

**Usage in Entities:**

```typescript
// domain/entities/user.entity.ts
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import type { Email } from "@/domain/value-objects/email.value-object";

export class User {
  private _isActive: boolean = true;
  private readonly _createdAt: Date;

  constructor(
    private readonly _id: UUIDv7,
    private _email: Email,
    private _name: string,
    private _hashedPassword: string
  ) {
    this._createdAt = new Date();
  }

  deactivate(): void {
    if (!this._isActive) {
      throw new Error(`User ${this._id.toString()} is already inactive`);
    }
    this._isActive = false;
  }

  get id(): UUIDv7 {
    return this._id;
  }

  get email(): Email {
    return this._email;
  }

  get name(): string {
    return this._name;
  }

  get isActive(): boolean {
    return this._isActive;
  }

  get createdAt(): Date {
    return this._createdAt;
  }
}
```

**Usage in Use Cases:**

```typescript
// application/use-cases/create-user.use-case.ts
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import { User } from "@/domain/entities/user.entity";
import { Email } from "@/domain/value-objects/email.value-object";

export class CreateUserUseCase {
  async execute(dto: CreateUserDto): Promise<UserResponseDto> {
    // Generate UUIDv7 for new user
    const id = UUIDv7.generate();
    const email = Email.create(dto.email);
    const user = new User(id, email, dto.name, dto.hashedPassword);

    await this.userRepository.save(user);

    return {
      id: user.id.toString(),
      email: user.email.toString(),
      name: user.name,
      isActive: user.isActive,
      createdAt: user.createdAt.toISOString(),
    };
  }
}
```

**Usage in Repositories:**

```typescript
// infrastructure/repositories/user.repository.impl.ts
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import { User } from "@/domain/entities/user.entity";

export class UserRepositoryImpl implements UserRepository {
  async findById(id: UUIDv7): Promise<User | null> {
    const row = await this.db
      .select()
      .from(users)
      .where(eq(users.id, id.toString()))
      .limit(1);

    if (!row) return null;

    // Reconstruct domain entity
    const userId = UUIDv7.from(row.id);
    const email = Email.create(row.email);
    return new User(userId, email, row.name, row.hashedPassword);
  }

  async save(user: User): Promise<void> {
    await this.db.insert(users).values({
      id: user.id.toString(),
      email: user.email.toString(),
      name: user.name,
      isActive: user.isActive,
      createdAt: user.createdAt,
    });
  }
}
```

**For complete implementation examples of Entities, Value Objects, and Repositories with Drizzle ORM, see `backend-engineer` skill**

### 2. Application Layer (Use Cases)

**Purpose**: Orchestrate business logic, implement use cases

**Contains:**

- Use Cases / Application Services
- DTOs (Data Transfer Objects)
- Mappers (Entity ↔ DTO)

**Rules:**

- ✅ Depends ONLY on Domain layer
- ✅ Orchestrates entities and value objects
- ✅ NO direct infrastructure dependencies (use interfaces)
- ✅ Stateless services

**Example Structure:**

```
src/application/
├── use-cases/
│   ├── create-user.use-case.ts
│   ├── update-user-profile.use-case.ts
│   └── deactivate-user.use-case.ts
├── dtos/
│   ├── create-user.dto.ts
│   └── user-response.dto.ts
└── mappers/
    └── user.mapper.ts
```

**Use Case Responsibilities:**

1. **Validate** business rules
2. **Orchestrate** domain objects (entities, value objects)
3. **Persist** through repositories (ports)
4. **Coordinate** side effects (events, notifications)
5. **Return** DTOs (never expose domain entities)

**Port (Interface) Example:**

```typescript
// ✅ Port in Domain layer (domain/ports/repositories/user.repository.ts)
// NO "I" prefix
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

export interface UserRepository {
  save(user: User): Promise<void>;
  findById(id: UUIDv7): Promise<User | undefined>;
  findByEmail(email: string): Promise<User | undefined>;
}

// Implementation in Infrastructure layer
```

**For complete Use Case examples with DTOs, Mappers, and orchestration patterns, see `backend-engineer` skill**

### 3. Infrastructure Layer (Adapters)

**Purpose**: Implement technical details and external dependencies

**Contains:**

- Repository implementations (implements domain/ports/repositories)
- Adapters (external service implementations)
- Database access (Drizzle ORM)
- HTTP setup (Hono app, middleware, OpenAPI)
- Configuration

**Rules:**

- ✅ Implements interfaces defined in Domain layer (ports)
- ✅ Contains framework-specific code
- ✅ Handles technical concerns
- ✅ NO business logic

**Example Structure:**

```
src/infrastructure/
├── controllers/
│   ├── user.controller.ts
│   ├── order.controller.ts
│   └── schemas/
│       ├── user.schema.ts
│       └── order.schema.ts
├── repositories/
│   ├── user.repository.impl.ts
│   └── order.repository.impl.ts
├── adapters/
│   ├── cache/
│   │   └── redis-cache.adapter.ts
│   ├── logger/
│   │   └── winston-logger.adapter.ts
│   └── queue/
│       ├── sqs-queue.adapter.ts
│       ├── localstack-sqs.adapter.ts
│       └── fake-queue.adapter.ts
├── http/
│   ├── server/
│   │   └── hono-http-server.adapter.ts
│   ├── middleware/
│   │   ├── auth.middleware.ts
│   │   ├── validation.middleware.ts
│   │   └── error-handler.middleware.ts
│   └── plugins/
│       ├── cors.plugin.ts
│       └── openapi.plugin.ts
├── database/
│   ├── drizzle/
│   │   ├── schema/
│   │   │   └── users.schema.ts
│   │   └── migrations/
│   └── connection.ts
└── container/
    └── main.ts
```

**Infrastructure Layer Responsibilities:**

- **Repositories**: Implement ports from `domain/ports/repositories/` using Drizzle ORM
- **Adapters**: Implement external service ports (Cache, Logger, Queue)
- **Controllers**: Self-registering HTTP controllers (thin layer, delegate to use cases)
  - Schemas: Zod validation schemas for HTTP contracts (requests/responses)
- **HTTP Layer**: Framework-specific HTTP handling
  - Server: Hono adapter (implements HttpServer port)
  - Middleware: HTTP middleware (auth, validation, error handling)
  - Plugins: Hono plugins (CORS, compression, OpenAPI, etc.)
- **Database**: Drizzle schemas, migrations, connection management
- **Container**: DI Container (composition root)
- **NO business logic**: Only technical implementation details

**Repository Pattern:**

```typescript
// Port in domain/ports/repositories/user.repository.ts
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

export interface UserRepository {
  save(user: User): Promise<void>;
  findById(id: UUIDv7): Promise<User | undefined>;
}

// Implementation in infrastructure/repositories/user.repository.impl.ts
export class UserRepositoryImpl implements UserRepository {
  // Drizzle ORM implementation
}
```

**For complete Repository and Adapter implementations with Drizzle ORM, Redis, and other infrastructure examples, see `backend-engineer` skill**

### 4. HTTP Layer (Framework-Specific, in Infrastructure)

**Purpose**: Handle HTTP requests, WebSocket connections, CLI commands

**Location**: `infrastructure/http/`

**Contains:**

- Server: Hono adapter (implements HttpServer port)
- Controllers: Self-registering HTTP controllers (route registration + handlers)
- Schemas: Zod validation for requests/responses
- Middleware: HTTP middleware (auth, validation, error handling)
- Plugins: Hono plugins (CORS, compression, OpenAPI, etc.)

**Rules:**

- ✅ Part of Infrastructure layer (HTTP is technical detail)
- ✅ Depends on Application layer (Use Cases) and HttpServer port
- ✅ Thin layer - delegates to Use Cases
- ✅ NO business logic
- ✅ Controllers auto-register routes in constructor

**Example Structure:**

```
src/infrastructure/http/
├── server/
│   └── hono-http-server.adapter.ts
├── controllers/
│   ├── user.controller.ts
│   └── order.controller.ts
├── schemas/
│   ├── user.schema.ts
│   └── order.schema.ts
├── middleware/
│   ├── auth.middleware.ts
│   └── error-handler.middleware.ts
└── plugins/
    ├── cors.plugin.ts
    └── openapi.plugin.ts
```

**Controller Responsibilities:**

- **Thin layer**: Validation + Delegation to Use Cases
- **NO business logic**: Controllers should be lightweight
- **Request validation**: Use Zod schemas at entry point
- **Response formatting**: Return DTOs (never domain entities)
- **Self-registering**: Controllers register routes in constructor via HttpServer port

**Controller Pattern (Self-Registering):**

```typescript
// infrastructure/http/controllers/user.controller.ts

/**
 * UserController
 *
 * Infrastructure layer (HTTP) - handles HTTP requests.
 * Thin layer that delegates to use cases.
 *
 * Pattern: Constructor Injection + Auto-registration
 */
import type { HttpServer } from "@/domain/ports/http-server";
import { HttpMethod } from "@/domain/ports/http-server";
import type { CreateUserUseCase } from "@/application/use-cases/create-user.use-case";

export class UserController {
  constructor(
    private readonly httpServer: HttpServer, // ✅ HttpServer port injected
    private readonly createUserUseCase: CreateUserUseCase // ✅ Use case injected
  ) {
    this.registerRoutes(); // ✅ Auto-register routes in constructor
  }

  private registerRoutes(): void {
    // POST /users - Create new user
    this.httpServer.route(HttpMethod.POST, "/users", async (context) => {
      try {
        const dto = context.req.valid("json"); // Validated by middleware
        const user = await this.createUserUseCase.execute(dto);
        return context.json(user, 201);
      } catch (error) {
        console.error("Error creating user:", error);
        return context.json({ error: "Internal server error" }, 500);
      }
    });
  }
}
```

**HttpServer Port (Domain Layer):**

```typescript
// domain/ports/http-server.ts
export enum HttpMethod {
  GET = "GET",
  POST = "POST",
  PUT = "PUT",
  DELETE = "DELETE",
  PATCH = "PATCH",
}

export type HttpHandler = (context: unknown) => Promise<Response | unknown>;

export interface HttpServer {
  route(method: HttpMethod, url: string, handler: HttpHandler): void;
  listen(port: number): void;
}
```

**Key Benefits:**

- ✅ **Framework-agnostic domain** - HttpServer port in domain layer
- ✅ **Testable** - Easy to mock HttpServer for testing controllers
- ✅ **DI-friendly** - Controllers resolve via container
- ✅ **Auto-registration** - Controllers register themselves in constructor
- ✅ **Thin controllers** - Only route registration + delegation
- ✅ **Clean separation** - No routes/ folder needed

**For complete HttpServer implementation (Hono adapter), Zod validation, and middleware patterns, see `backend-engineer` skill**

## Dependency Injection

**Use custom DI Container (NO external libraries like InversifyJS or TSyringe)**

### Why Dependency Injection?

- Enables testability (inject mocks)
- Follows Dependency Inversion Principle
- Centralized dependency management
- Supports different lifetimes (singleton, scoped, transient)

### DI Principles in Clean Architecture

**Constructor Injection:**

```typescript
// ✅ Use cases depend on abstractions (ports), not implementations
export class CreateUserUseCase {
  constructor(
    private readonly userRepository: UserRepository, // Port from domain/ports/
    private readonly passwordHasher: PasswordHasher, // Port from domain/ports/
    private readonly emailService: EmailService // Port from domain/ports/
  ) {}

  async execute(dto: CreateUserDto): Promise<UserResponseDto> {
    // Orchestrate domain logic using injected dependencies
  }
}
```

**Lifetimes:**

- **singleton**: Core infrastructure (config, database, logger, repositories)
- **scoped**: Per-request instances (use cases, controllers)
- **transient**: New instance every time (rarely used)

**For complete DI Container implementation with Symbol-based tokens, registration patterns, and Hono integration, see `backend-engineer` skill**

## Testing Strategy

### Domain Layer Tests (Pure Unit Tests)

```typescript
// ✅ Easy to test - no dependencies
import { describe, expect, it } from "bun:test";
import { User } from "@/domain/entities/user.entity";
import { Email } from "@/domain/value-objects/email.value-object";
import { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

describe("User Entity", () => {
  it("should deactivate user", () => {
    const userId = UUIDv7.generate();
    const email = Email.create("user@example.com");
    const user = new User(userId, email, "John Doe", "hashed_password");

    user.deactivate();

    expect(user.isActive).toBe(false);
  });

  it("should throw error when deactivating already inactive user", () => {
    const userId = UUIDv7.generate();
    const email = Email.create("user@example.com");
    const user = new User(userId, email, "John Doe", "hashed_password");
    user.deactivate();

    expect(() => user.deactivate()).toThrow();
  });
});
```

### Application Layer Tests (With Mocks)

```typescript
// ✅ Test use case with mocked ports
import { describe, expect, it, mock } from "bun:test";
import { CreateUserUseCase } from "@/application/use-cases/create-user.use-case";

describe("CreateUserUseCase", () => {
  it("should create user successfully", async () => {
    // Arrange - Mock dependencies
    const mockRepository = {
      save: mock(async () => {}),
      findByEmail: mock(async () => undefined),
    };

    const mockPasswordHasher = {
      hash: mock(async (password: string) => `hashed_${password}`),
    };

    const mockEmailService = {
      sendWelcomeEmail: mock(async () => {}),
    };

    const useCase = new CreateUserUseCase(
      mockRepository as any,
      mockPasswordHasher as any,
      mockEmailService as any
    );

    const dto = {
      email: "test@example.com",
      password: "password123",
      name: "Test User",
    };

    // Act
    const result = await useCase.execute(dto);

    // Assert
    expect(mockRepository.save).toHaveBeenCalledTimes(1);
    expect(mockPasswordHasher.hash).toHaveBeenCalledWith("password123");
    expect(mockEmailService.sendWelcomeEmail).toHaveBeenCalledTimes(1);
    expect(result.email).toBe("test@example.com");
  });
});
```

## Common Patterns

### Repository Pattern

```typescript
// Port (Domain layer - domain/ports/repositories/order.repository.ts)
// NO "I" prefix
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";

export interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: UUIDv7): Promise<Order | undefined>;
  findByUserId(userId: UUIDv7): Promise<Order[]>;
}

// Adapter (Infrastructure layer - infrastructure/repositories/order.repository.impl.ts)
export class OrderRepositoryImpl implements OrderRepository {
  async save(order: Order): Promise<void> {
    // Drizzle ORM implementation
  }
}
```

### Domain Service

```typescript
// ✅ Domain service when logic involves multiple entities
export class PricingService {
  calculateOrderTotal(order: Order, discountRules: DiscountRule[]): Money {
    let total = Money.zero();

    for (const item of order.items) {
      total = total.add(item.price.multiply(item.quantity));
    }

    for (const rule of discountRules) {
      if (rule.appliesTo(order)) {
        total = total.subtract(rule.calculateDiscount(total));
      }
    }

    return total;
  }
}
```

### Event-Driven Communication

```typescript
// Domain Event
import type { UUIDv7 } from "@/domain/value-objects/uuidv7.value-object";
import type { Email } from "@/domain/value-objects/email.value-object";

export class UserCreatedEvent {
  constructor(
    public readonly userId: UUIDv7,
    public readonly email: Email,
    public readonly occurredAt: Date = new Date()
  ) {}
}

// Use Case publishes event
export class CreateUserUseCase {
  async execute(dto: CreateUserDto): Promise<UserResponseDto> {
    // ... create user ...

    await this.eventBus.publish(new UserCreatedEvent(user.id, user.email)); // user.id is UUIDv7

    return UserMapper.toDto(user);
  }
}
```

## Anti-Patterns to Avoid

### ❌ Anemic Domain Model

```typescript
// ❌ Bad - Just data, no behavior
export class User {
  id: string;
  email: string;
  isActive: boolean;
}

// Business logic in service (wrong layer)
export class UserService {
  deactivateUser(user: User): void {
    user.isActive = false;
  }
}
```

**Fix:** Move behavior into entity:

```typescript
// ✅ Good - Rich domain model
export class User {
  deactivate(): void {
    if (!this._isActive) {
      throw new UserAlreadyInactiveError(this._id);
    }
    this._isActive = false;
  }
}
```

### ❌ Domain Layer Depending on Infrastructure

```typescript
// ❌ Bad - Domain depends on infrastructure
import { db } from "@/infrastructure/database";

export class User {
  async save(): Promise<void> {
    await db.insert(users).values(this); // WRONG!
  }
}
```

**Fix:** Keep domain pure, use repository:

```typescript
// ✅ Good - Pure domain, repository handles persistence
export class User {
  // Pure domain logic, no database access
}

// Repository in infrastructure/repositories/
export class UserRepositoryImpl implements UserRepository {
  async save(user: User): Promise<void> {
    await db.insert(users).values(...);
  }
}
```

### ❌ Fat Controllers

```typescript
// ❌ Bad - Business logic in controller
app.post('/users', async (c) => {
  const data = c.req.valid('json');

  // Validation
  if (!data.email.includes('@')) {
    return c.json({ error: 'Invalid email' }, 400);
  }

  // Check if exists
  const exists = await db.select()...;

  // Hash password
  const hashed = await bcrypt.hash(data.password, 10);

  // Save
  await db.insert(users).values(...);

  // Send email
  await sendgrid.send(...);

  return c.json(user, 201);
});
```

**Fix:** Delegate to use case:

```typescript
// ✅ Good - Thin controller
app.post("/users", zValidator("json", CreateUserSchema), async (c) => {
  const dto = c.req.valid("json");
  const user = await createUserUseCase.execute(dto);
  return c.json(user, 201);
});
```

## Migration Strategy

### From Monolith to Clean Architecture

1. **Start with Use Cases** - Extract business logic into use cases
2. **Create Domain Models** - Move entities and value objects to domain layer
3. **Define Ports** - Create interfaces in application layer
4. **Implement Adapters** - Move infrastructure code behind interfaces
5. **Refactor Controllers** - Make them thin, delegate to use cases

## Best Practices

### Do:

- ✅ Keep domain layer pure (no external dependencies)
- ✅ Use interfaces (ports) for all external dependencies
- ✅ Implement rich domain models with behavior
- ✅ Make use cases orchestrate domain logic
- ✅ Test domain logic without infrastructure
- ✅ Use dependency injection at composition root
- ✅ Keep controllers thin (validation + delegation)

### Don't:

- ❌ Put business logic in controllers or repositories
- ❌ Let domain layer depend on infrastructure
- ❌ Create anemic domain models
- ❌ Mix layers (e.g., use Drizzle in domain layer)
- ❌ Skip interfaces (ports) for infrastructure
- ❌ Make use cases depend on concrete implementations

## Remember

- **The Dependency Rule is sacred** - Always point inward
- **Domain is the core** - Everything revolves around it
- **Test the domain first** - It's the most important part
- **Interfaces enable flexibility** - Easy to swap implementations
- **Clean Architecture is about maintainability** - Not perfection

