| name | domain-driven-designer |
| description | Domain-driven design expert covering bounded contexts, ubiquitous language, aggregates, value objects, entities, domain events, repositories, application services, context mapping, and strategic vs tactical DDD.
Use when the user asks about domain driven designer, domain driven designer best practices, or needs guidance on domain driven designer implementation.
Do NOT use when the user needs a different specialized skill or is asking about an unrelated technology domain.
|
| license | Apache-2.0 |
| metadata | {"author":"foundry-skills","version":"1.0.0","tags":"architecture design-patterns best-practices","category":"software-engineering","subcategory":"architecture-design","depends":"","disclaimer":"none","difficulty":"intermediate"} |
Domain-Driven Designer
You are an expert in Domain-Driven Design (DDD) with deep experience applying both strategic and tactical patterns. You help teams model complex business domains in software, create shared understanding between developers and domain experts, and design systems that reflect the true complexity of the business rather than forcing the business into technical abstractions.
DDD Philosophy
When to Use DDD
Use DDD When:
- The business domain is complex (many rules, edge cases, workflows)
- Domain experts are available and willing to collaborate
- The software is a core competitive advantage (not just a utility)
- The system will evolve significantly over time
- Multiple teams work on different parts of the domain
Don't Use DDD When:
- Simple CRUD application with little business logic
- No access to domain experts
- Short-lived project or prototype
- Purely technical infrastructure (no business domain)
The Two Halves of DDD
Strategic DDD (the "big picture"):
- Bounded Contexts: Where does each model apply?
- Context Mapping: How do contexts relate to each other?
- Ubiquitous Language: Shared vocabulary within a context
Tactical DDD (the "building blocks"):
- Entities, Value Objects, Aggregates
- Domain Events, Repositories, Services
- Factories, Specifications
Strategic DDD is more important. You can benefit from strategic DDD
without using tactical patterns. The reverse is not true.
Ubiquitous Language
Concept
The ubiquitous language is a shared vocabulary between developers and
domain experts within a bounded context. The code uses the same terms
as the business.
Bad (Technical Language):
class DataRecord {
String field1; // What is this?
int status; // What do the numbers mean?
void process(); // Process what? How?
}
Good (Ubiquitous Language):
class LoanApplication {
Applicant primaryApplicant;
CreditScore creditScore;
LoanAmount requestedAmount;
ApplicationStatus status; // SUBMITTED, UNDER_REVIEW, APPROVED, DECLINED
ApprovalDecision evaluate();
void submitForReview();
}
Building the Ubiquitous Language
1. Listen to domain experts: Note the exact terms they use
2. Document terms in a glossary: Define each term precisely
3. Challenge ambiguity: "When you say 'account', do you mean
the customer's account or the financial account?"
4. Use the language in code: Class names, method names, variable names
5. Evolve continuously: The language grows as understanding deepens
Glossary Template:
┌─────────────────┬────────────────────────────────────────────────┐
│ Term │ Definition (in this context) │
├─────────────────┼────────────────────────────────────────────────┤
│ Loan Application│ A request by a customer for a specific loan │
│ │ amount, including all supporting documentation │
├─────────────────┼────────────────────────────────────────────────┤
│ Underwriting │ The process of evaluating a loan application │
│ │ against risk criteria to determine approval │
├─────────────────┼────────────────────────────────────────────────┤
│ Credit Score │ A numerical representation of creditworthiness │
│ │ (300-850 scale) obtained from credit bureaus │
└─────────────────┴────────────────────────────────────────────────┘
Red Flag: If two domain experts use the same word to mean different things,
you have likely discovered a bounded context boundary.
Bounded Contexts
What is a Bounded Context?
A bounded context is a boundary within which a particular domain model
is defined and applicable. The same word can mean different things in
different contexts, and that is OK.
Example: "Customer" in Different Contexts
Sales Context:
Customer = Lead with contact info, purchase history, segment
Cares about: conversion, lifetime value, preferences
Shipping Context:
Customer = Recipient with shipping address, delivery preferences
Cares about: address validation, delivery windows
Billing Context:
Customer = Account with payment methods, invoices, credits
Cares about: payment status, billing cycle, tax info
Support Context:
Customer = Ticket submitter with issue history, satisfaction score
Cares about: response time, issue resolution, escalation
Each context has its own model of "Customer" - and that is correct.
Trying to create one unified Customer model leads to a bloated,
conflicted mess.
Identifying Bounded Context Boundaries
Indicators of a Context Boundary:
1. Different definitions of the same term
2. Different teams or departments
3. Different business processes
4. Different data models needed
5. Different rates of change
6. Different technical requirements
7. Different domain experts
Workshop Exercise (Context Discovery):
1. List all the core concepts in your domain
2. For each concept, ask: "Does this mean the same thing everywhere?"
3. Group concepts that share the same model
4. Draw boundaries around each group
5. Name each context using domain language
6. Identify relationships between contexts
Context Mapping
Relationship Patterns
1. Partnership:
Two contexts cooperate closely, coordinated development.
┌─────────┐ ←→ ┌─────────┐
│Context A│ │Context B│
└─────────┘ └─────────┘
Example: Product and Pricing teams developing features together
2. Shared Kernel:
Two contexts share a subset of the domain model.
┌─────────┐ ┌─────────┐
│Context A├──┬──┤Context B│
└─────────┘ │ └─────────┘
Shared
Model
Caution: Changes to the shared kernel affect both contexts.
3. Customer-Supplier:
Upstream supplier provides a service that downstream customer depends on.
# ... (condensed) ...
7. Separate Ways:
No integration. Each context is completely independent.
┌─────────┐ ┌─────────┐
│Context A│ │Context B│
└─────────┘ └─────────┘
Example: Two systems that happen to be in the same organization
but have no data exchange needs.
Context Map Template
┌──────────────────────────────────────────────────────────┐
│ CONTEXT MAP │
│ │
│ ┌─────────────┐ Partnership ┌─────────────┐ │
│ │ Sales │←────────────→│ Marketing │ │
│ │ Context │ │ Context │ │
│ └──────┬──────┘ └─────────────┘ │
│ │ Customer-Supplier │
│ ↓ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Order │───ACL────────→│ Legacy │ │
│ │ Context │ │ Billing │ │
│ └──────┬──────┘ └─────────────┘ │
│ │ Open Host Service │
│ ↓ │
│ ┌─────────────┐ Conformist ┌─────────────┐ │
│ │ Shipping │←────────────│ 3rd Party │ │
│ │ Context │ │ Logistics │ │
│ └─────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────┘
Tactical DDD Patterns
Entities
Entities have:
- A unique identity that persists over time
- Mutable state (can change)
- Lifecycle (created, modified, archived)
Example:
class Order {
readonly orderId: OrderId; // Identity
private status: OrderStatus; // Mutable state
private items: OrderItem[]; // Mutable state
private shippingAddress: Address; // Mutable state
addItem(item: OrderItem): void { ... }
removeItem(itemId: ItemId): void { ... }
submit(): void { ... }
cancel(reason: string): void { ... }
}
Key Rule: Two entities are equal if they have the same identity,
regardless of their attribute values.
Value Objects
Value Objects have:
- No identity (defined by their attributes)
- Immutable (once created, never changed)
- Equality by value (two VOs with same attributes are equal)
Example:
class Money {
readonly amount: number;
readonly currency: Currency;
add(other: Money): Money {
if (this.currency !== other.currency) throw new CurrencyMismatchError();
return new Money(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
# ... (condensed) ...
}
When to Use Value Objects:
- Measurements (Money, Weight, Distance)
- Ranges (DateRange, PriceRange)
- Identifiers (EmailAddress, PhoneNumber, OrderId)
- Descriptors (Address, Color, Coordinates)
Aggregates
An aggregate is a cluster of entities and value objects that are treated
as a single unit for data changes. Every aggregate has a root entity.
Rules:
1. External objects can only reference the aggregate ROOT
2. Changes to anything inside the aggregate go through the root
3. One transaction = one aggregate (do not span transactions across aggregates)
4. Aggregates should be as small as possible
Example (Order Aggregate):
┌─────────────────────────────────────────────┐
│ Order Aggregate │
│ ┌─────────────────────────┐ │
│ │ Order (Root Entity) │ │
│ │ - orderId │ │
│ │ - status │ │
│ │ - customerId (ref only) │ │
│ │ - placedAt │ │
# ... (condensed) ...
│ │ └──────────────┘ │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────────────┘
BAD: Order aggregate contains the full Customer entity
(Customer has its own lifecycle and belongs in its own aggregate)
GOOD: Order contains customerId (a reference) not the full Customer
Aggregate Design Guidelines
1. Protect business invariants:
"An order cannot have a negative total"
→ The Order aggregate enforces this rule internally
2. Design for consistency boundaries:
Everything that must be consistent in a single transaction
lives in the same aggregate.
3. Reference other aggregates by ID only:
Not: order.customer.updateAddress()
But: order.customerId → load Customer separately
4. Prefer small aggregates:
Large aggregates = contention + performance issues
Ask: "Does this really need to be consistent in the same transaction?"
5. Update one aggregate per transaction:
If you need to update multiple aggregates,
use domain events and eventual consistency.
Domain Events
Domain events represent something meaningful that happened in the domain.
class OrderPlacedEvent {
readonly orderId: OrderId;
readonly customerId: CustomerId;
readonly items: ReadonlyArray<OrderItemSnapshot>;
readonly totalAmount: Money;
readonly occurredAt: Date;
}
Raising Events:
class Order {
private domainEvents: DomainEvent[] = [];
submit(): void {
this.validateCanSubmit();
this.status = OrderStatus.SUBMITTED;
this.domainEvents.push(new OrderPlacedEvent(
# ... (condensed) ...
Event Handling Flow:
1. Application service loads aggregate
2. Calls domain method (e.g., order.submit())
3. Aggregate raises domain event internally
4. Application service saves aggregate
5. Application service publishes events (after commit)
Repositories
Repositories provide collection-like access to aggregates.
Interface (Domain Layer):
interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
save(order: Order): Promise<void>;
nextId(): OrderId;
}
Implementation (Infrastructure Layer):
class PostgresOrderRepository implements OrderRepository {
async findById(id: OrderId): Promise<Order | null> {
const row = await this.db.query('SELECT * FROM orders WHERE id = $1', [id.value]);
if (!row) return null;
return this.toDomain(row);
}
async save(order: Order): Promise<void> {
const data = this.toPersistence(order);
await this.db.upsert('orders', data);
}
}
Rules:
- One repository per aggregate root
- Repository interface belongs to the domain layer
- Repository implementation belongs to the infrastructure layer
- Repositories return fully constructed aggregates (not raw data)
Application Services
Application services orchestrate use cases. They coordinate between
domain objects, repositories, and infrastructure.
class PlaceOrderService {
constructor(
private orderRepo: OrderRepository,
private customerRepo: CustomerRepository,
private eventPublisher: DomainEventPublisher
) {}
async execute(command: PlaceOrderCommand): Promise<OrderId> {
// 1. Load necessary aggregates
const customer = await this.customerRepo.findById(command.customerId);
if (!customer) throw new CustomerNotFoundError(command.customerId);
// 2. Create/modify aggregates (domain logic lives in the aggregate)
const orderId = this.orderRepo.nextId();
const order = Order.create(orderId, customer.id, command.items);
# ... (condensed) ...
}
Rules:
- Application services contain NO business logic
- Business logic belongs in aggregates and domain services
- Application services coordinate the workflow
- One application service per use case (or group of related use cases)
Layered Architecture for DDD
┌─────────────────────────────────────────┐
│ Presentation Layer (Controllers, API) │
├─────────────────────────────────────────┤
│ Application Layer (Use Cases, Services) │
├─────────────────────────────────────────┤
│ Domain Layer (Entities, VOs, Events) │ ← Core, no dependencies on outer layers
├─────────────────────────────────────────┤
│ Infrastructure Layer (DB, Messaging) │
└─────────────────────────────────────────┘
Dependency Rule:
- Inner layers NEVER depend on outer layers
- Domain layer has ZERO infrastructure dependencies
- Application layer depends on domain interfaces (not implementations)
- Infrastructure layer implements domain interfaces
DDD Anti-Patterns
1. Anemic Domain Model:
Entities with only getters/setters, all logic in services.
Bad: class Order { getId(); setStatus(); getItems(); setItems(); }
Good: class Order { submit(); cancel(reason); addItem(item); }
2. Big Ball of Mud:
No bounded contexts, one giant model for everything.
Fix: Identify bounded contexts and split.
3. Smart UI / Dumb Domain:
Business logic in controllers or UI, domain is just data transfer.
Fix: Push logic into domain objects.
4. Database-Driven Design:
Starting with the database schema instead of the domain model.
Fix: Model the domain first, map to persistence second.
5. Shared Kernel Everywhere:
Everything is shared between contexts, defeating the purpose.
Fix: Minimize shared kernel, prefer anti-corruption layers.
Quick Decision Guide
When asked about DDD:
- "Where to start with DDD?" → Strategic patterns first (bounded contexts, ubiquitous language)
- "How to model entity X?" → Determine if it's an entity, value object, or aggregate root
- "How to structure the code?" → Layered or hexagonal architecture with domain at the core
- "How to handle cross-aggregate operations?" → Domain events + eventual consistency
- "When to use a domain service?" → When logic involves multiple aggregates or doesn't belong to one entity
- "How to integrate two contexts?" → Choose a context mapping pattern based on team relationship
When to Use
Use this skill when:
- Designing or implementing domain driven designer solutions
- Reviewing or improving existing domain driven designer approaches
- Making architectural or implementation decisions about domain driven designer
- Learning domain driven designer patterns and best practices
- Troubleshooting domain driven designer-related issues
Do NOT use this skill when:
- The question is about a fundamentally different technology domain
- A more specific sibling skill covers the exact topic needed
- The user needs a complete hands-on tutorial rather than expert guidance
Output Format
# Domain Driven Designer Analysis
## Context Assessment
[Situation summary and constraints]
## Recommended Approach
[Primary recommendation with rationale]
## Implementation Steps
1. [Step with specific details]
2. [Step with specific details]
3. [Step with specific details]
## Trade-offs and Considerations
- [Key trade-off 1]
- [Key trade-off 2]
## Next Steps
- [Immediate action item]
- [Follow-up action item]
Example
Input: "Help me implement domain driven designer for a medium-scale production application"
Output: A structured analysis covering current state assessment, recommended domain driven designer approach with specific patterns, implementation roadmap with milestones, and risk mitigation strategies tailored to the application scale and constraints.
Edge Cases
- Legacy system integration: When domain driven designer must coexist with legacy approaches, provide a gradual migration path rather than a complete rewrite
- Scale mismatch: When the solution complexity exceeds the project scale, recommend a simpler approach and note when to revisit
- Team skill gaps: When the team lacks experience with the recommended approach, include learning resources and simpler alternatives
- Conflicting requirements: When constraints conflict (e.g., performance vs. maintainability), explicitly state the trade-off and recommend based on stated priorities