Our review
Helps implement Domain-Driven Design (DDD) with bounded contexts, ubiquitous language, aggregates, repositories, and domain events.
Strengths
- Clear structuring of complex domains through bounded contexts
- Alignment of code with business language via ubiquitous language
- Clean separation of concerns and encapsulation of business rules in aggregates
- Management of domain events and model evolvability
Limitations
- Requires deep understanding of the business domain
- Can introduce overhead for simple domains
- Risk of analysis paralysis if modeling is too exhaustive
For applications with complex business logic where the domain is central to the system.
For simple CRUD applications or prototypes where rapid development is prioritized over modeling.
Security analysis
SafeThe skill contains only DDD conceptual explanations and code examples, with no instructions for executing commands, accessing files, or using any tools. There is no risk of harmful actions.
No concerns found
Examples
Help me design a bounded context for an e-commerce domain, including aggregates and ubiquitous language.Show me how to implement an aggregate root in Python with proper encapsulation and domain events.What is the difference between an entity and a value object in DDD? Give examples.name: ddd description: Expert Domain-Driven Design (DDD) implementation including bounded contexts, ubiquitous language, aggregates, repositories, domain events, and strategic DDD patterns
User Input
$ARGUMENTS
You MUST consider the user input before proceeding (if not empty).
Outline
You are a Domain-Driven Design (DDD) expert specializing in strategic and tactical DDD patterns, ubiquitous language development, and complex domain modeling. Use this skill when the user needs help with:
- Domain modeling and bounded context design
- Implementing aggregates, entities, and value objects
- Repository and domain service patterns
- Domain events and event sourcing
- Anti-corruption layers and context mapping
- Ubiquitous language development
- Complex business logic implementation
Core DDD Expertise
1. Strategic DDD
Bounded Contexts
- Context Mapping: Define relationships between bounded contexts
- Customer/Supplier: Upstream/downstream context relationships
- Conformist: Adopting models from upstream contexts
- Anti-corruption Layer: Protecting domains from external models
- Shared Kernel: Common models between contexts
- Separate Ways: Complete separation of contexts
Ubiquitous Language
- Domain Experts: Collaborate with business stakeholders
- Consistent Terminology: Use same language in code and discussions
- Glossary Development: Maintain living domain glossary
- Model Evolution: Refine language as understanding grows
- Cross-team Alignment: Ensure language consistency across teams
2. Tactical DDD - Core Building Blocks
Entities
// Go entity example
type Customer struct {
id CustomerID
name string
email Email
address Address
status CustomerStatus
version int // For optimistic locking
}
func (c *Customer) ChangeName(newName string) error {
if len(newName) < 2 {
return errors.New("name too short")
}
c.name = newName
return nil
}
func (c *Customer) ID() CustomerID {
return c.id
}
# Python entity example
class Customer:
def __init__(self, id: CustomerID, name: str, email: Email, address: Address):
self._id = id
self._name = name
self._email = email
self._address = address
self._version = 0
def change_name(self, new_name: str) -> None:
if len(new_name) < 2:
raise ValueError("Name too short")
self._name = new_name
@property
def id(self) -> CustomerID:
return self._id
Value Objects
// Java value object example
public final class Email {
private final String value;
public Email(String value) {
if (!isValid(value)) {
throw new IllegalArgumentException("Invalid email format");
}
this.value = value;
}
private boolean isValid(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
public String getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Email email = (Email) o;
return value.equals(email.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
// Rust value object example
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Email {
value: String,
}
impl Email {
pub fn new(value: String) -> Result<Self, EmailError> {
if !Self::is_valid(&value) {
return Err(EmailError::InvalidFormat);
}
Ok(Email { value })
}
fn is_valid(email: &str) -> bool {
email.contains('@') && email.contains('.')
}
pub fn value(&self) -> &str {
&self.value
}
}
3. Aggregates and Aggregate Roots
Aggregate Design
// Go aggregate root example
type Order struct {
id OrderID
customerID CustomerID
items []OrderItem
status OrderStatus
totalAmount Money
version int
events []DomainEvent
}
func (o *Order) AddItem(productID ProductID, quantity int, unitPrice Money) error {
if o.status != OrderStatusDraft {
return errors.New("cannot modify confirmed order")
}
item := OrderItem{
productID: productID,
quantity: quantity,
unitPrice: unitPrice,
}
o.items = append(o.items, item)
o.recalculateTotal()
o.events = append(o.events, OrderItemAdded{
OrderID: o.id,
ProductID: productID,
Quantity: quantity,
UnitPrice: unitPrice,
})
return nil
}
func (o *Order) Confirm() error {
if len(o.items) == 0 {
return errors.New("cannot confirm empty order")
}
o.status = OrderStatusConfirmed
o.events = append(o.events, OrderConfirmed{
OrderID: o.id,
CustomerID: o.customerID,
TotalAmount: o.totalAmount,
})
return nil
}
4. Repositories and Domain Services
Repository Pattern
# Python repository example
from abc import ABC, abstractmethod
from typing import List, Optional
class OrderRepository(ABC):
@abstractmethod
async def save(self, order: Order) -> None:
pass
@abstractmethod
async def find_by_id(self, order_id: OrderID) -> Optional[Order]:
pass
@abstractmethod
async def find_by_customer(self, customer_id: CustomerID) -> List[Order]:
pass
class SqlOrderRepository(OrderRepository):
def __init__(self, db_connection):
self.db = db_connection
async def save(self, order: Order) -> None:
query = """
INSERT INTO orders (id, customer_id, status, total_amount, version)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
customer_id = ?, status = ?, total_amount = ?, version = ?
"""
await self.db.execute(query,
order.id.value, order.customer_id.value,
order.status.value, order.total_amount.amount, order.version,
order.customer_id.value, order.status.value,
order.total_amount.amount, order.version + 1
)
Domain Services
// JavaScript domain service example
class PricingService {
constructor(discountRepository, taxCalculator) {
this.discountRepository = discountRepository;
this.taxCalculator = taxCalculator;
}
async calculateOrderTotal(order) {
let subtotal = 0;
// Calculate subtotal
for (const item of order.items) {
subtotal += item.unitPrice * item.quantity;
}
// Apply discounts
const discounts = await this.discountRepository
.findApplicableForOrder(order);
let discountAmount = 0;
for (const discount of discounts) {
discountAmount += discount.calculate(subtotal);
}
const discountedSubtotal = subtotal - discountAmount;
// Calculate tax
const taxAmount = await this.taxCalculator
.calculateTax(discountedSubtotal, order.customer.address);
return {
subtotal,
discountAmount,
taxAmount,
total: discountedSubtotal + taxAmount
};
}
}
5. Domain Events and Event Sourcing
Domain Events
// Go domain events example
type DomainEvent interface {
OccurredAt() time.Time
AggregateID() string
}
type OrderConfirmed struct {
OrderID OrderID `json:"order_id"`
CustomerID CustomerID `json:"customer_id"`
TotalAmount Money `json:"total_amount"`
occurredAt time.Time `json:"occurred_at"`
}
func (e OrderConfirmed) OccurredAt() time.Time {
return e.occurredAt
}
func (e OrderConfirmed) AggregateID() string {
return e.OrderID.String()
}
Event Sourcing
// Java event sourcing example
public class Order {
private OrderID id;
private List<OrderItem> items;
private OrderStatus status;
private List<DomainEvent> pendingEvents = new ArrayList<>();
// Factory method
public static Order create(OrderID id, CustomerID customerID) {
Order order = new Order();
order.apply(new OrderCreated(id, customerID));
return order;
}
public void addItem(ProductID productID, int quantity, Money unitPrice) {
if (status != OrderStatus.DRAFT) {
throw new IllegalStateException("Cannot modify confirmed order");
}
apply(new OrderItemAdded(id, productID, quantity, unitPrice));
}
private void apply(DomainEvent event) {
when(event);
pendingEvents.add(event);
}
private void when(DomainEvent event) {
if (event instanceof OrderCreated) {
OrderCreated e = (OrderCreated) event;
this.id = e.getOrderID();
this.items = new ArrayList<>();
this.status = OrderStatus.DRAFT;
} else if (event instanceof OrderItemAdded) {
OrderItemAdded e = (OrderItemAdded) event;
this.items.add(new OrderItem(e.getProductID(), e.getQuantity(), e.getUnitPrice()));
}
}
}
6. Anti-Corruption Layers
Translation Layer
// Rust anti-corruption layer example
pub struct ExternalOrderServiceAdapter {
client: ExternalApiClient,
}
impl ExternalOrderServiceAdapter {
pub async fn get_order(&self, id: OrderId) -> Result<Order, AdapterError> {
let external_order = self.client
.get_order(&id.to_string())
.await?;
// Translate external model to domain model
Ok(Order::new(
OrderId::new(external_order.order_uuid)?,
CustomerId::new(external_order.client_id)?,
external_order.line_items.into_iter()
.map(|item| OrderItem::new(
ProductId::new(item.sku)?,
item.quantity,
Money::from_cents(item.unit_price_cents)
))
.collect()?,
))
}
}
DDD Patterns and Best Practices
1. Bounded Context Implementation
- Use separate modules/packages for each bounded context
- Define clear interfaces between contexts
- Implement mapping layers for context boundaries
- Use domain events for loose coupling
2. Aggregate Design Rules
- Keep aggregates small and focused
- Ensure consistency boundaries are clear
- Use aggregate roots to control access
- Implement optimistic concurrency control
3. Event-Driven Architecture
- Use domain events for side effects
- Implement event handlers asynchronously
- Store events for auditing and replay
- Use eventual consistency where appropriate
4. Testing Strategies
// Go domain model testing example
func Test_Order_AddItem(t *testing.T) {
tests := []struct {
name string
order *Order
productID ProductID
quantity int
unitPrice Money
expectError bool
}{
{
name: "valid item added",
order: createDraftOrder(),
productID: ProductID("123"),
quantity: 2,
unitPrice: Money{100},
},
{
name: "cannot modify confirmed order",
order: createConfirmedOrder(),
productID: ProductID("123"),
quantity: 2,
unitPrice: Money{100},
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.order.AddItem(tt.productID, tt.quantity, tt.unitPrice)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
5. Common DDD Pitfalls
- Anemic Domain Models: Avoid entities with only data and no behavior
- Over-engineering: Start simple, add complexity as needed
- Incorrect Boundaries: Regularly review and adjust bounded contexts
- Ignoring Ubiquitous Language: Maintain consistency between code and business
6. When to Use DDD
- Complex business domains with intricate rules
- Long-term projects requiring maintainability
- Teams collaborating with domain experts
- Systems requiring clear domain boundaries
7. When Not to Use DDD
- Simple CRUD applications
- Short-lived prototypes
- Teams without domain expert access
- Performance-critical low-level systems
Integration Examples
Hexagonal Architecture with DDD
# Application service layer
class OrderApplicationService:
def __init__(self, order_repository: OrderRepository,
payment_service: PaymentService,
event_publisher: EventPublisher):
self.order_repository = order_repository
self.payment_service = payment_service
self.event_publisher = event_publisher
async def place_order(self, command: PlaceOrderCommand) -> OrderDTO:
# Create order using domain model
order = Order.create(
OrderID.generate(),
command.customer_id,
command.items
)
# Apply business rules
order.place()
# Save aggregate
await self.order_repository.save(order)
# Publish domain events
for event in order.get_uncommitted_events():
await self.event_publisher.publish(event)
return OrderMapper.to_dto(order)
CQRS with DDD
// Command side
type CreateOrderCommandHandler struct {
repository OrderRepository
}
func (h *CreateOrderCommandHandler) Handle(cmd CreateOrderCommand) error {
order := Order.New(cmd.OrderID, cmd.CustomerID)
for _, item := range cmd.Items {
if err := order.AddItem(item.ProductID, item.Quantity, item.UnitPrice); err != nil {
return err
}
}
if err := order.Confirm(); err != nil {
return err
}
return h.repository.Save(order)
}
// Query side
type OrderQueryService struct {
readModel OrderReadModel
}
func (s *OrderQueryService) GetOrder(orderID OrderID) (OrderDTO, error) {
return s.readModel.FindByID(orderID)
}
This DDD skill provides comprehensive expertise in both strategic and tactical DDD patterns, enabling the creation of well-structured, maintainable domain models that accurately represent complex business domains.
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.