skillby re-cinq

ddd

Expert Domain-Driven Design (DDD) implementation including bounded contexts, ubiquitous language, aggregates, repositories, domain events, and strategic DDD patterns

Installs: 1
Used in: 1 repos
Updated: 4h ago
$npx ai-builder add skill re-cinq/ddd

Installs to .claude/skills/ddd/

## User Input

```text
$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
// 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
# 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
// 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
// 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
// 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
# 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
// 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
// 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
// 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
// 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
// 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
```python
# 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
```go
// 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.

Quick Install

$npx ai-builder add skill re-cinq/ddd

Details

Type
skill
Author
re-cinq
Slug
re-cinq/ddd
Created
2d ago

More by re-cinq