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/dddInstalls 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/dddDetails
- Type
- skill
- Author
- re-cinq
- Slug
- re-cinq/ddd
- Created
- 2d ago