ddd
// Expert Domain-Driven Design (DDD) implementation including bounded contexts, ubiquitous language, aggregates, repositories, domain events, and strategic DDD patterns
$ git log --oneline --stat
stars:7
forks:1
updated:March 4, 2026
SKILL.mdreadonly
SKILL.md Frontmatter
nameddd
descriptionExpert 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.