ddd-refactor
npx machina-cli add skill radkomih/claude-code-ext/ddd-refactor --openclawDDD Refactor Skill
Comprehensive Domain-Driven Design refactoring framework based on Eric Evans' DDD principles.
When to Use This Skill
- Refactoring domain models and business logic
- Identifying anemic domain models
- Defining bounded contexts
- Applying tactical DDD patterns (Entity, Value Object, Aggregate)
- Improving domain clarity and ubiquitous language
- Analyzing code for DDD anti-patterns
- Architecting domain-centric systems
Prerequisites
CRITICAL: Always load the DDD principles first:
Read: plugins/ddd-refactor/resources/ddd-principles.json
This JSON contains 19+ principles with definitions, code smells, and refactoring guidance.
Refactoring Approach
Four-Phase Strategy
Phase 1: Discovery & Analysis (15-20 min)
Understand the Domain:
- Identify core domain vs supporting/generic subdomains
- Map existing bounded contexts (explicit or implicit)
- Document current ubiquitous language usage
- Analyze domain model structure
Scan for Code Smells:
- Anemic domain models (all logic in services)
- Missing ubiquitous language (technical jargon)
- Blurred bounded context boundaries
- Mutable value objects
- Large aggregates without clear boundaries
- Domain logic in application/infrastructure layers
- Missing domain events
- Repositories exposing internal entities
- Bidirectional dependencies across contexts
Technical Exploration:
# Find potential entities
grep -r "class.*Entity" --include="*.{js,ts,java,cs,py}"
# Find service classes (potential logic that belongs in domain)
grep -r "class.*Service" --include="*.{js,ts,java,cs,py}"
# Find repositories
grep -r "Repository" --include="*.{js,ts,java,cs,py}"
# Find domain events (or lack thereof)
grep -r "Event\|event" --include="*.{js,ts,java,cs,py}"
Phase 2: Strategic Refactoring Plan (10-15 min)
Based on loaded DDD principles:
-
Define Bounded Contexts
- Map natural domain boundaries
- Identify context relationships (Shared Kernel, Customer-Supplier, etc.)
- Plan integration strategies
-
Establish Ubiquitous Language
- List terms needing renaming
- Extract implicit concepts into explicit models
- Create domain glossary
-
Prioritize Refactoring
- Critical: Core domain models, aggregate boundaries, data consistency
- High: Entity/Value Object patterns, domain events, repositories
- Medium: Services, factories, layering
- Low: Supporting subdomains, infrastructure
Phase 3: Tactical Pattern Application (30-45 min)
Apply patterns systematically:
1. Entities - Identity-based objects
- Extract objects with lifecycle and identity
- Add identity equality (not value equality)
- Encapsulate business rules within entities
- Make state changes explicit through methods
2. Value Objects - Immutable descriptive objects
- Convert primitive obsession to value objects
- Make all value objects immutable
- Implement value-based equality
- Create self-validating value objects
3. Aggregates - Consistency boundaries
- Group related entities under aggregate roots
- Enforce invariants at aggregate boundaries
- Make internal entities inaccessible from outside
- Use aggregate roots for all external access
4. Domain Events - Significant occurrences
- Identify state changes that matter to the domain
- Publish events from aggregates after state changes
- Use events to decouple bounded contexts
- Record event history for audit/debugging
5. Repositories - Aggregate persistence
- Create repositories only for aggregate roots
- Return reconstituted aggregates (not raw data)
- Abstract all storage concerns
- Use specification pattern for complex queries
6. Domain Services - Stateless operations
- Extract operations that don't belong to any entity
- Keep services thin (orchestration, not logic)
- Separate Domain/Application/Infrastructure services
- Make service operations explicit domain concepts
7. Factories - Complex creation
- Encapsulate complex aggregate construction
- Hide creation details from clients
- Ensure invariants are met at creation
- Provide convenient creation methods
8. Layered Architecture
- Move domain logic from controllers to domain layer
- Isolate external systems with anti-corruption layers
- Ensure dependencies point inward (domain is innermost)
- Keep domain layer free of infrastructure concerns
Phase 4: Validation & Testing (10-15 min)
Verify Improvements:
- Ubiquitous language is reflected in code
- Business rules are explicit and testable
- Bounded contexts have clear boundaries
- Aggregates enforce their invariants
- Domain events capture significant changes
- Repositories work with aggregate roots
- Domain layer is free of infrastructure
- Integration points are explicit and controlled
Testing Strategy:
- Unit test entity/value object logic
- Test aggregate invariant enforcement
- Test domain event publication
- Integration test repositories
- Test anti-corruption layers
Core DDD Principles Reference
Strategic Design
- Ubiquitous Language - Domain vocabulary in code
- Bounded Context - Explicit model boundaries
- Context Map - Relationships between contexts
- Continuous Integration - Prevent fragmentation
Tactical Patterns
- Entity - Identity-based objects with lifecycle
- Value Object - Immutable, attribute-based objects
- Aggregate - Consistency boundary and transaction scope
- Repository - Collection-like access to aggregates
- Factory - Complex object creation
- Domain Service - Stateless domain operations
- Domain Event - Record of domain occurrence
Architecture
- Layered Architecture - Separation of concerns
- Anti-Corruption Layer - Isolation from external systems
Context Mapping
- Shared Kernel - Explicit sharing between contexts
- Customer-Supplier - Upstream-downstream relationship
- Conformist - Adopt upstream model
- Open Host Service - Standardized API
- Published Language - Documented interchange format
Code Smell Detection Checklist
Strategic Anti-Patterns
- No explicit bounded contexts
- Technical names instead of domain language
- Mixed business logic across contexts
- No context integration strategy
- Same entity meaning different things in different places
Tactical Anti-Patterns
- Anemic domain model (getters/setters only)
- Entities without clear identity
- Mutable value objects
- Large aggregates (>5-7 entities)
- Repositories for non-root entities
- Domain logic in services instead of entities
- No domain events for state changes
- Primitive obsession (no value objects)
Architecture Anti-Patterns
- Domain logic in controllers/UI
- Direct database access from domain
- No anti-corruption layer for external systems
- Infrastructure concerns in domain layer
- Bidirectional dependencies between contexts
Output Format
1. Anti-Pattern Identified
File: src/orders/OrderService.java:45-78
Smell: Anemic domain model - Order entity has only getters/setters
Principle Violated: Entity Pattern
Impact: Business logic scattered in services, hard to test and maintain
2. DDD Principle to Apply
Principle: Entity (from ddd-principles.json)
Category: Tactical Pattern
Key Point: Entities should encapsulate business rules and behavior
When to Apply: Objects with identity that change over time
3. Refactoring Steps
Step 1: Move validation logic from OrderService to Order entity
Step 2: Add domain methods: order.cancel(), order.ship(), order.addItem()
Step 3: Enforce invariants within entity methods
Step 4: Raise domain events for significant state changes
Step 5: Update OrderService to orchestrate, not contain logic
4. Code Example
// BEFORE: Anemic Domain Model (Anti-pattern)
class Order {
private id: string;
private items: OrderItem[];
private status: string;
// Only getters and setters
getId(): string { return this.id; }
getStatus(): string { return this.status; }
setStatus(status: string): void { this.status = status; }
}
class OrderService {
cancelOrder(order: Order): void {
// Business logic in service layer
if (order.getStatus() === 'SHIPPED') {
throw new Error('Cannot cancel shipped order');
}
order.setStatus('CANCELLED');
this.emailService.sendCancellationEmail(order);
}
}
// AFTER: Rich Domain Model (DDD Pattern)
class Order {
private id: OrderId;
private items: OrderItem[];
private status: OrderStatus;
private events: DomainEvent[] = [];
constructor(id: OrderId, items: OrderItem[]) {
this.id = id;
this.items = items;
this.status = OrderStatus.PENDING;
}
// Business logic in entity
cancel(): void {
if (!this.canBeCancelled()) {
throw new OrderCannotBeCancelledException(
'Cannot cancel order in status: ' + this.status
);
}
this.status = OrderStatus.CANCELLED;
this.recordEvent(new OrderCancelled(this.id, new Date()));
}
private canBeCancelled(): boolean {
return this.status !== OrderStatus.SHIPPED
&& this.status !== OrderStatus.DELIVERED;
}
getDomainEvents(): DomainEvent[] {
return [...this.events];
}
private recordEvent(event: DomainEvent): void {
this.events.push(event);
}
}
// Service becomes thin orchestrator
class OrderService {
constructor(
private orderRepository: OrderRepository,
private eventBus: EventBus
) {}
async cancelOrder(orderId: string): Promise<void> {
const order = await this.orderRepository.findById(orderId);
order.cancel(); // Domain logic stays in domain
await this.orderRepository.save(order);
// Publish events for other bounded contexts
order.getDomainEvents().forEach(event => {
this.eventBus.publish(event);
});
}
}
5. Impact Assessment
Benefits:
- Business rules are explicit and self-documenting
- Order enforces its own invariants
- Easier to test (unit test the entity)
- Domain events enable loose coupling
- Service layer is thin and focused
Metrics:
- Reduced cyclomatic complexity in services
- Increased testability (pure domain logic)
- Better domain language alignment
- Clearer responsibility boundaries
Language-Specific Patterns
TypeScript/JavaScript
// Value Object with immutability
class Money {
constructor(
private readonly amount: number,
private readonly currency: string
) {
if (amount < 0) throw new Error('Amount cannot be negative');
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new Error('Currency mismatch');
}
return new Money(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount
&& this.currency === other.currency;
}
}
Java
// Aggregate with encapsulation
public class Order {
private final OrderId id;
private final List<OrderLine> lines;
private OrderStatus status;
// Package-private constructor (use factory)
Order(OrderId id) {
this.id = Objects.requireNonNull(id);
this.lines = new ArrayList<>();
this.status = OrderStatus.DRAFT;
}
public void addLine(Product product, Quantity quantity) {
if (this.status != OrderStatus.DRAFT) {
throw new OrderAlreadySubmittedException();
}
this.lines.add(new OrderLine(product, quantity));
}
// Factory method
public static Order create(OrderId id) {
return new Order(id);
}
}
Python
from dataclasses import dataclass
from typing import List
# Value Object with frozen dataclass
@dataclass(frozen=True)
class EmailAddress:
value: str
def __post_init__(self):
if '@' not in self.value:
raise ValueError('Invalid email address')
# Entity with behavior
class Customer:
def __init__(self, customer_id: str, email: EmailAddress):
self._id = customer_id
self._email = email
self._events: List[DomainEvent] = []
def change_email(self, new_email: EmailAddress) -> None:
if self._email != new_email:
old_email = self._email
self._email = new_email
self._events.append(
EmailChanged(self._id, old_email, new_email)
)
Best Practices
Do
- Start with the domain model, not the database
- Use domain language everywhere (code, tests, docs)
- Make implicit concepts explicit
- Favor immutability where possible
- Test domain logic in isolation
- Keep aggregates small (2-5 entities max)
- Use domain events for inter-context communication
- Apply anti-corruption layers for external systems
Don't
- Don't let infrastructure drive the domain model
- Don't skip domain events for significant changes
- Don't make value objects mutable
- Don't expose aggregate internals
- Don't create repositories for non-aggregate roots
- Don't put domain logic in services
- Don't share entities across bounded contexts
- Don't over-engineer - apply DDD where complexity justifies it
Resources
- DDD Principles: See
resources/ddd-principles.jsonfor complete definitions - Checklist: See
CHECKLIST.mdfor full anti-pattern list - Book: "Domain-Driven Design" by Eric Evans
Workflow Integration
This skill can be:
- Invoked from
/refactorcommand - Used by
ddd-analyzeragent for autonomous analysis - Triggered by pre-commit hooks to check for anti-patterns
- Called from other skills for domain model improvements
Source
git clone https://github.com/radkomih/claude-code-ext/blob/master/plugins/ddd-refactor/skills/ddd-refactor/SKILL.mdView on GitHub Overview
ddd-refactor provides a structured approach to refactor code using Domain-Driven Design principles. It helps identify DDD anti-patterns, improve ubiquitous language, and apply both tactical and strategic patterns to align software with the domain.
How This Skill Works
Prerequisites load DDD principles first via the provided JSON. The workflow then moves through Discovery & Analysis to map domains, boundaries, and language, followed by a Strategic Refactoring Plan and Tactical Pattern Application, culminating in refactoring code with Entities, Value Objects, Aggregates, Domain Events, and Repositories.
When to Use It
- Refactoring domain models and business logic to align with core domain concepts
- Identifying anemic domain models and moving logic into rich domain objects
- Defining bounded contexts and clarifying context boundaries
- Applying tactical patterns (Entities, Value Objects, Aggregates) and domain events
- Improving ubiquitous language and overall domain clarity across the codebase
Quick Start
- Step 1: Load DDD principles: Read: plugins/ddd-refactor/resources/ddd-principles.json
- Step 2: Run Phase 1 Discovery & Analysis to map domain boundaries and identify anti-patterns
- Step 3: Apply Phase 3 Tactical Pattern Application to implement Entities, Value Objects, Aggregates, Domain Events, and Repositories
Best Practices
- Always load DDD principles first ( Read: plugins/ddd-refactor/resources/ddd-principles.json )
- Map bounded contexts and establish a ubiquitous language before changing code
- Prioritize critical core-domain changes and preserve invariants at aggregate boundaries
- Isolate domain logic inside entities, aggregates, and domain services rather than in application layers
- Use domain events and repositories to decouple contexts and persist aggregates
Example Use Cases
- Refactoring anemic domain models into rich entities with behavior and invariants
- Splitting a monolith into bounded contexts with explicit context relationships
- Introducing domain events for state changes and event-driven integration
- Replacing primitive types with value objects to remove primitive obsession
- Persisting aggregates via repositories and reconstituting them from storage