skills/oracle-architect/SKILL.md
DDD and hexagonal architecture with functional core pattern. Use when designing features, modeling domains, breaking down tasks, or understanding component responsibilities.
npx skillsauth add martinffx/claude-code-atelier oracle-architectInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Precise vocabulary for every architectural decision. Use these terms exactly — consistency is the point.
Key principles (apply to every decision):
Domain-Driven Design and hexagonal architecture with functional core pattern for feature design.
Unified view of functional core and effectful edge:
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 │ │ │
└─────────────────────────────────┘ └──────────────────────────┘
Key Principle: Business logic lives in the functional core (Service + Entity). IO operations live in the effectful edge. Core defines interfaces; edge implements them (dependency inversion).
Before creating a new module, run these checks:
Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.
When to apply: Before extracting a new Service, Repository, or utility class.
Example:
❌ Shallow: OrderFormatterService with 1 method per field
→ Deleting it just moves 6 lines into the Router
→ Interface is as complex as implementation
✅ Deep: OrderService with validate → reserve → save → publish
→ Deleting it replicates orchestration across N handlers
→ Small interface (createOrder), large implementation
Callers and tests cross the same seam. If you want to test past the interface, the module is probably the wrong shape.
Implication:
One adapter = hypothetical seam. Two adapters = real seam.
When to apply: Deciding whether to extract an interface.
Examples:
// ❌ Hypothetical seam — only one adapter exists
interface IEmailClient { send(email: Email): Promise<void>; }
class SendgridClient implements IEmailClient { ... }
// No second adapter. The interface adds indirection without value.
// ✅ Real seam — two adapters exist
interface IOrderRepository { save(order: Order): Promise<Order>; }
class PostgresOrderRepository implements IOrderRepository { ... }
class InMemoryOrderRepository implements IOrderRepository { ... } // for tests
// The seam earns its keep because callers vary (production vs test).
Prefer deep modules over shallow ones:
| Module | Interface Size | Implementation | Depth |
|--------|--------------|----------------|-------|
| OrderValidator | 3 methods (validate, validateItems, validateAddress) | 30 lines each | Shallow |
| Order (entity) | 4 methods (fromRequest, toRecord, toResponse, validate) | 200 lines of rules, transforms, invariants | Deep |
Rule of thumb: A module's interface should hide at least 3x the complexity it exposes.
Pure, deterministic components containing all business logic.
Responsibility: Orchestrate business operations, coordinate between entities and repositories.
Characteristics:
Example:
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));
}
}
Responsibility: Domain models, validation, business rules, data transformations.
Characteristics:
Example:
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);
}
}
IO-performing components that interact with the outside world.
Responsibility: HTTP request handling, parsing, response formatting.
Characteristics:
Example:
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 });
}
});
Responsibility: Data persistence and retrieval.
Characteristics:
Example:
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 });
}
}
Quick reference for where things belong:
| 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 |
Implementation order follows dependency chain:
1. Entity → Domain models, validation, transforms
2. Repository → Data access interfaces and implementations
3. Service → Business logic orchestration
4. Router → HTTP endpoints
Rationale: Each layer depends on layers below. Can't implement service without entity, can't implement router without service.
One task per layer:
For complex features, break down further:
Architectural decisions inform testing strategy:
Architect 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
The testing skill uses architectural structure to determine:
For detailed patterns and examples:
development
Security architecture and threat modeling knowledge. Auto-invokes when designing features that handle untrusted data, authentication, authorization, external integrations, file uploads, or sensitive data. Provides risk assessment frameworks, trust boundary analysis, and security design principles — not implementation code.
testing
Adversarial review of non-trivial decisions using fresh-context scrutiny. Use when correctness matters more than speed, when stakes are high (production, security-sensitive logic, irreversible operations), or before committing significant architectural or implementation choices.
development
Compact the current conversation into a handoff document for another agent to pick up.
testing
Socratic interrogation of plans against the project's domain model and documented decisions. Use when the user wants to stress-test a plan, clarify terminology, or validate assumptions against existing domain language. Updates CONTEXT.md and ADRs inline as decisions crystallise.