Loading...
Loading...
DDD and hexagonal architecture with functional core pattern. Use when designing features, modeling domains, breaking down tasks, or understanding component responsibilities.
npx skill4agent add martinffx/claude-code-atelier atelier-spec-architect Effectful Edge (IO) Functional Core (Pure)
┌─────────────────────────────────┐ ┌──────────────────────────┐
│ Router → request parsing │ │ Service → orchestration│
│ Consumer → event handling │───▶│ Entity → domain rules │
│ Client → external APIs │ │ → validation │
│ Producer → event publishing │◀───│ → transforms │
│ Repository→ data persistence │ │ │
└─────────────────────────────────┘ └──────────────────────────┘class OrderService {
async createOrder(request: CreateOrderRequest): Promise<Result<Order>> {
// Validate with entity
const order = Order.fromRequest(request);
const validation = order.validate();
if (!validation.ok) return validation;
// Check business rules
const inventory = await this.inventoryRepo.checkAvailability(order.items);
if (!inventory.available) return Err('Items not available');
// Coordinate persistence
await this.inventoryRepo.reserve(order.items);
const saved = await this.orderRepo.save(order.toRecord());
return Ok(Order.fromRecord(saved));
}
}class Order {
constructor(
public readonly id: string,
public readonly customerId: string,
public readonly items: OrderItem[],
public readonly status: OrderStatus,
public readonly total: number
) {}
static fromRequest(req: CreateOrderRequest): Order {
return new Order(
generateId(),
req.customerId,
req.items.map(i => new OrderItem(i)),
'pending',
req.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
);
}
toRecord(): OrderRecord {
return {
id: this.id,
customer_id: this.customerId,
items: JSON.stringify(this.items),
status: this.status,
total: this.total
};
}
validate(): Result<Order> {
if (this.items.length === 0) {
return Err('Order must have at least one item');
}
if (this.total < 0) {
return Err('Order total cannot be negative');
}
return Ok(this);
}
canCancel(): boolean {
return ['pending', 'confirmed'].includes(this.status);
}
}router.post('/orders', async (req, res) => {
const result = await orderService.createOrder(req.body);
if (result.ok) {
res.status(201).json(result.value.toResponse());
} else {
res.status(400).json({ error: result.error });
}
});class OrderRepository {
async save(record: OrderRecord): Promise<OrderRecord> {
return await db.orders.create(record);
}
async findById(id: string): Promise<OrderRecord | null> {
return await db.orders.findOne({ id });
}
}| Concern | Component | Layer | Testability |
|---|---|---|---|
| Domain model | Entity | Core | Unit test (pure) |
| Validation | Entity | Core | Unit test (pure) |
| Business rules | Entity | Core | Unit test (pure) |
| Orchestration | Service | Core | Unit test (stub repos) |
| Data transforms | Entity | Core | Unit test (pure) |
| HTTP parsing | Router | Edge | Integration test |
| Data access | Repository | Edge | Integration test |
| External APIs | Client | Edge | Integration test |
| Event handling | Consumer | Edge | Integration test |
| Event publishing | Producer | Edge | Integration test |
1. Entity → Domain models, validation, transforms
2. Repository → Data access interfaces and implementations
3. Service → Business logic orchestration
4. Router → HTTP endpointsArchitect Outputs → Testing Inputs
────────────────────────────────────────────────
Component responsibilities → What to test
Layer boundaries → Where to test
Pure vs effectful → Unit vs integration
Entity transformations → Property-based tests
Service orchestration → Stub-driven tests