Domain-Driven Design

SkillMedia

Ddd is a pack that adds a command to update CLAUDE.md with best practices for domain-driven development. Once added, your AI can follow guidance on Clean Architecture, SOLID principles, and other design patterns to keep your code quality high.

Use Domain-Driven Design in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add Domain-Driven Design and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use the Domain-Driven Design skill

Details

Instructions available. Your AI can read the instructions. Execution depends on the setup they require.

After adding the pack, run the command it provides to update CLAUDE.md with the best practices. Your AI will then apply that guidance when working on your code.

Domain-Driven DesignStart free

What your AI can do with it

  • Update CLAUDE.md with domain-driven development best practices
  • Follow Clean Architecture guidance when building or changing code
  • Apply SOLID principles to keep code well structured
  • Use established design patterns to improve code quality
  • Keep code quality guidance in one place your AI reads

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/architecture/ddd/SKILL.md and read by Ahel’s review.

Strategic DDD

┌─────────────────┐     ┌─────────────────┐
│  Order Context   │────▶│ Payment Context  │
│  (core domain)   │     │  (supporting)    │
└────────┬────────┘     └─────────────────┘
         │
         ▼
┌─────────────────┐     ┌─────────────────┐
│ Inventory Context│     │ Notification Ctx  │
│  (supporting)    │     │  (generic)       │
└─────────────────┘     └─────────────────┘

Tactical DDD (TypeScript)

Entity

class Order {
  private constructor(
    readonly id: OrderId,
    private items: OrderItem[],
    private status: OrderStatus,
    private readonly createdAt: Date,
  ) {}

  static create(items: OrderItem[]): Order {
    if (items.length === 0) throw new DomainError('Order must have at least one item');
    return new Order(OrderId.generate(), items, OrderStatus.PENDING, new Date());
  }

  get total(): Money {
    return this.items.reduce((sum, item) => sum.add(item.subtotal), Money.zero('USD'));
  }

  confirm(): DomainEvent[] {
    if (this.status !== OrderStatus.PENDING) throw new DomainError('Can only confirm pending orders');
    this.status = OrderStatus.CONFIRMED;
    return [new OrderConfirmed(this.id, this.total)];
  }
}

Value Object

class Money {
  private constructor(readonly amount: number, readonly currency: string) {
    if (amount < 0) throw new DomainError('Amount cannot be negative');
  }

  static of(amount: number, currency: string): Money {
    return new Money(Math.round(amount * 100) / 100, currency);
  }

  static zero(currency: string): Money { return new Money(0, currency); }

  add(other: Money): Money {
    if (this.currency !== other.currency) throw new DomainError('Currency mismatch');
    return Money.of(this.amount + other.amount, this.currency);
  }

  equals(other: Money): boolean {
    return this.amount === other.amount && this.currency === other.currency;
  }
}

Domain Event

class OrderConfirmed implements DomainEvent {
  readonly occurredAt = new Date();
  constructor(readonly orderId: OrderId, readonly total: Money) {}
}

Repository (Port)

interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  save(order: Order): Promise<void>;
  nextId(): OrderId;
}

Application Service

class ConfirmOrderUseCase {
  constructor(
    private orders: OrderRepository,
    private eventBus: EventBus,
  ) {}

  async execute(orderId: string): Promise<void> {
    const order = await this.orders.findById(OrderId.from(orderId));
    if (!order) throw new NotFoundError('Order', orderId);

    const events = order.confirm();
    await this.orders.save(order);
    await this.eventBus.publishAll(events);
  }
}

DDD Building Blocks

Building BlockPurposeIdentity?Mutable?
EntityDomain object with identityYes (ID)Yes
Value ObjectImmutable descriptorNo (structural equality)No
AggregateConsistency boundaryRoot entity has IDYes (via root)
Domain EventSomething that happenedEvent IDNo
RepositoryPersistence abstractionN/AN/A
Domain ServiceLogic not belonging to entityN/AN/A

Anti-Patterns

Anti-PatternFix
Anemic domain model (logic in services)Put business logic in entities
Aggregate too largeKeep aggregates small, reference by ID
Exposing entity internalsUse methods that express domain intent
Cross-aggregate transactionsUse domain events for eventual consistency
Repository returning DTOsReturn domain objects, map in application layer

Production Checklist

  • Bounded contexts identified and documented
  • Ubiquitous language in code matches business terms
  • Aggregates enforce invariants
  • Value objects for all descriptors (Money, Email, Address)
  • Domain events for cross-context communication
  • Repository pattern for persistence abstraction

Signals

GitHub stars
40
Forks
7
Last commit
Oct 2026
Advanced
Item type
skill
Key
ddd
Source
github.com/claude-dev-suite/claude-dev-suite