Domain-Driven Design
SkillMediaDdd 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.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
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.
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 Block | Purpose | Identity? | Mutable? |
|---|---|---|---|
| Entity | Domain object with identity | Yes (ID) | Yes |
| Value Object | Immutable descriptor | No (structural equality) | No |
| Aggregate | Consistency boundary | Root entity has ID | Yes (via root) |
| Domain Event | Something that happened | Event ID | No |
| Repository | Persistence abstraction | N/A | N/A |
| Domain Service | Logic not belonging to entity | N/A | N/A |
Anti-Patterns
| Anti-Pattern | Fix |
|---|---|
| Anemic domain model (logic in services) | Put business logic in entities |
| Aggregate too large | Keep aggregates small, reference by ID |
| Exposing entity internals | Use methods that express domain intent |
| Cross-aggregate transactions | Use domain events for eventual consistency |
| Repository returning DTOs | Return 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
github.com/claude-dev-suite/claude-dev-suite