# Ddd

> Domain-Driven Design rules - bounded contexts, entities, value objects, aggregates, repositories and domain events

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

---


# Domain-Driven Design (DDD) — Rules

---

## 1. Philosophy

1. **Domain first** — Business logic drives design. Technology follows.
2. **Ubiquitous language** — Shared vocabulary between domain experts and developers.
3. **Bounded contexts** — Explicit boundaries. No shared models across contexts.
4. **Strategic before tactical** — Context mapping before entities.
5. **Model reflects reality** — Code reads like domain specification.

---

## 2. Ubiquitous Language

- **Glossary** — Maintain living document: term → definition → context
- **Code speaks language** — `Order.place()`, not `OrderService.create()`
- **Events are facts** — `OrderPlaced`, not `OrderCreatedEvent`
- **No technical jargon in domain** — no `DTO`, `Entity`,
  `Repository` in domain layer

---

## 3. Bounded Contexts

### Context Map

```text
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│  Catalog    │────▶│  Orders     │◀───│  Shipping   │
│  Context    │     │  Context    │    │  Context    │
└─────────────┘     └─────────────┘    └─────────────┘
       │                   │                   │
       ▼                   ▼                   ▼
  Product,          Order,              Shipment,
  Category          Customer            Tracking
```

### Integration Patterns

| Pattern                   | When                               | Coupling |
| ------------------------- | ---------------------------------- | -------- |
| **Shared Kernel**         | Common subset, tight collaboration | High     |
| **Customer/Supplier**     | One drives, other follows          | Medium   |
| **Conformist**            | Downstream adapts to upstream      | Low      |
| **Anti-Corruption Layer** | Legacy/external systems            | Low      |
| **Separate Ways**         | No relationship                    | None     |

### Rules

- **Explicit boundaries** — separate packages/modules per context
- **No shared database** — each context owns its data
- **Events for integration** — domain events, not direct calls
- **Context map documented** — living diagram

---

## 4. Tactical Building Blocks

### Entities

```ts
// domain/order/Order.ts
export class Order {
  private constructor(
    public readonly id: OrderId,
    public readonly customerId: CustomerId,
    private items: OrderItem[],
    private status: OrderStatus,
    private readonly placedAt: Date,
  ) {}

  static place(customerId: CustomerId, items: OrderItem[]): Order {
    if (items.length === 0) throw new Error("Order must have items");
    return new Order(
      OrderId.generate(),
      customerId,
      items,
      "pending",
      new Date(),
    );
  }

  addItem(item: OrderItem): void {
    this.ensureEditable();
    this.items.push(item);
  }

  cancel(): void {
    this.ensureEditable();
    this.status = "cancelled";
    DomainEvents.raise(new OrderCancelled(this.id));
  }

  private ensureEditable(): void {
    if (this.status !== "pending") throw new Error("Order not editable");
  }
}
```

### Value Objects

```ts
// domain/shared/Email.ts
export class Email {
  private constructor(public readonly value: string) {}

  static create(value: string): Email {
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      throw new Error("Invalid email format");
    }
    return new Email(value.toLowerCase());
  }

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

// Usage
const email = Email.create("user@example.com");
```

### Aggregates

```ts
// Aggregate Root = Entity that controls access to children
export class Order {
  // ... entity code

  // Children only accessible through root
  getItems(): ReadonlyArray<OrderItem> {
    return [...this.items];
  }

  // Invariants enforced at aggregate boundary
  addItem(item: OrderItem): void {
    const existing = this.items.find((i) => i.productId.equals(item.productId));
    if (existing) existing.increaseQuantity(item.quantity);
    else this.items.push(item);
    this.recalculateTotal();
  }
}
```

### Rules Tactical Building Blocks

- **Entities have identity** — `id` + `equals()`
- **Value objects immutable** — no setters, `equals()` by value
- **Aggregate root controls children** — no direct child access
- **Invariants in aggregate** — business rules enforced at boundary
- **Factory methods** — `Order.place()`, not `new Order()`

---

## 5. Repositories

### Interface (Domain Layer)

```ts
// domain/order/OrderRepository.ts
export interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  findByCustomer(customerId: CustomerId): Promise<Order[]>;
  save(order: Order): Promise<void>;
  delete(id: OrderId): Promise<void>;
}
```

### Implementation (Infrastructure Layer)

```ts
// infrastructure/repositories/PrismaOrderRepository.ts
export class PrismaOrderRepository implements OrderRepository {
  constructor(private prisma: PrismaClient) {}

  async findById(id: OrderId): Promise<Order | null> {
    const data = await this.prisma.order.findUnique({
      where: { id: id.value },
    });
    return data ? this.toDomain(data) : null;
  }

  async save(order: Order): Promise<void> {
    await this.prisma.order.upsert({
      where: { id: order.id.value },
      create: this.toPersistence(order),
      update: this.toPersistence(order),
    });
  }

  private toDomain(data: any): Order {
    /* ... */
  }
  private toPersistence(order: Order): any {
    /* ... */
  }
}
```

### Rules Repositories

- **Interface in domain** — implementation in infrastructure
- **One repository per aggregate root** — not per entity
- **Return domain objects** — not DTOs/ORM models
- **Transaction boundary** — repository manages unit of work

---

## 6. Domain Events

```ts
// domain/shared/DomainEvents.ts
type EventHandler = (event: DomainEvent) => Promise<void>;

class DomainEvents {
  private static handlers: Map<string, EventHandler[]> = new Map();

  static register(eventName: string, handler: EventHandler): void {
    const handlers = this.handlers.get(eventName) || [];
    handlers.push(handler);
    this.handlers.set(eventName, handlers);
  }

  static async raise(event: DomainEvent): Promise<void> {
    const handlers = this.handlers.get(event.constructor.name) || [];
    await Promise.all(handlers.map((h) => h(event)));
  }
}

// Event definition
export class OrderPlaced implements DomainEvent {
  constructor(
    public readonly orderId: OrderId,
    public readonly customerId: CustomerId,
    public readonly total: Money,
    public readonly occurredAt: Date = new Date(),
  ) {}
}

// Handler (in application layer)
DomainEvents.register("OrderPlaced", async (event: OrderPlaced) => {
  await emailService.sendConfirmation(event.customerId, event.orderId);
  await inventoryService.reserveItems(event.orderId);
});
```

### Rules Domain Events

- **Events are facts** — past tense, immutable
- **Raise in aggregate** — `DomainEvents.raise(new OrderPlaced(...))`
- **Handlers in application layer** — not domain layer
- **Async, eventually consistent** — no blocking

---

## 7. CQRS (Basic)

### Command (Write)

```ts
// application/orders/PlaceOrderCommand.ts
export class PlaceOrderCommand {
  constructor(
    public readonly customerId: string,
    public readonly items: { productId: string; quantity: number }[],
  ) {}
}

// Handler
export class PlaceOrderHandler {
  constructor(
    private orders: OrderRepository,
    private events: DomainEvents,
  ) {}

  async handle(cmd: PlaceOrderCommand): Promise<OrderId> {
    const customerId = new CustomerId(cmd.customerId);
    const items = cmd.items.map(
      (i) => new OrderItem(new ProductId(i.productId), i.quantity),
    );
    const order = Order.place(customerId, items);
    await this.orders.save(order);
    await this.events.raise(new OrderPlaced(order.id, customerId, order.total));
    return order.id;
  }
}
```

### Query (Read)

```ts
// application/orders/queries/GetOrderQuery.ts
export class GetOrderQuery {
  constructor(public readonly orderId: string) {}
}

// Handler (can use read model / projection)
export class GetOrderHandler {
  constructor(private readModel: OrderReadModel) {}

  async handle(query: GetOrderQuery): Promise<OrderView> {
    return this.readModel.findById(query.orderId);
  }
}
```

### Rules CQRS

- **Commands = writes** — return `void` or `id`
- **Queries = reads** — never mutate
- **Separate models** — write model (domain) ≠ read model (projection)
- **Eventual consistency** — read model updated via event handlers

---

## 8. Anti-Corruption Layer

```ts
// infrastructure/acl/LegacyCustomerAdapter.ts
export class LegacyCustomerAdapter implements CustomerRepository {
  constructor(private legacyApi: LegacyApiClient) {}

  async findById(id: CustomerId): Promise<Customer | null> {
    const dto = await this.legacyApi.getCustomer(id.value);
    if (!dto) return null;
    return this.toDomain(dto);
  }

  private toDomain(dto: LegacyCustomerDto): Customer {
    return new Customer(
      new CustomerId(dto.id),
      Email.create(dto.email),
      dto.name,
      dto.tier as CustomerTier,
    );
  }
}
```

### Rules Anti-Corruption Layer

- **Translate external → domain** — no leakage
- **Own the mapping** — own the translation logic
- **Isolate volatility** — external changes don't break domain

---

## 9. Decision Guide

| Scenario                       | Pattern                               |
| ------------------------------ | ------------------------------------- |
| Simple CRUD                    | Skip DDD — use Transaction Script     |
| Complex business rules         | Entities + Aggregates + Domain Events |
| Multiple teams/contexts        | Bounded Contexts + Context Map        |
| External integrations          | Anti-Corruption Layer                 |
| High read/write separation     | CQRS                                  |
| Audit trail / temporal queries | Event Sourcing                        |

---

## 10. TypeScript Patterns

### Branded Types

```ts
// types/branded.ts
type Brand<T, B> = T & { __brand: B };

type OrderId = Brand<string, "OrderId">;
type CustomerId = Brand<string, "CustomerId">;
type ProductId = Brand<string, "ProductId">;

// Factory
const OrderId = {
  generate: (): OrderId => crypto.randomUUID() as OrderId,
  from: (value: string): OrderId => value as OrderId,
};
```

### Result Pattern

```ts
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };

function ok<T>(value: T): Result<T, never> {
  return { ok: true, value };
}
function err<E>(error: E): Result<never, E> {
  return { ok: false, error };
}

// Usage
async function placeOrder(
  cmd: PlaceOrderCommand,
): Promise<Result<OrderId, OrderError>> {
  try {
    const order = await orderService.place(cmd);
    return ok(order.id);
  } catch (e) {
    return err(new OrderError("Failed to place order", e));
  }
}
```

---

## 10. Methodology

Before using ANY DDD pattern not documented in this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` + `context7_query-docs` for DDD, TypeScript.
2. **Official docs**: DDD books (Evans, Vernon), TypeScript docs — verify current patterns.
3. **Project config**: `domain/`, `application/`, `infrastructure/` — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.

---

## 11. Prohibitions

- ❌ Do not use anemic models — logic in entities, not services
- ❌ Do not expose ORM entities as domain objects
- ❌ Do not share aggregates across bounded contexts
- ❌ Do not put business logic in repositories
- ❌ Do not raise events outside aggregates
- ❌ Do not skip invariants — enforce at aggregate boundary
- ❌ Do not use DTOs in domain layer
- ❌ Do not query aggregates — use read models

---

## 12. References

> **Note:** For TypeScript rules, see [TypeScript](../typescript/SKILL.md)
> **Note:** For JavaScript conventions, see [JavaScript](../javascript/SKILL.md)
> **Note:** For Repository patterns, see [API Design](../api-design/SKILL.md)
> **Note:** For Event patterns, see [API Design](../api-design/SKILL.md)

---

Last updated: 2026-08

