| name | clean-architecture-ddd |
| description | Domain-Driven Design with Clean Architecture โ framework-agnostic patterns for entities, value objects, aggregates, use cases, and the dependency rule. Use PROACTIVELY when designing domain models, implementing bounded contexts, structuring layered architectures, separating business logic from infrastructure, or applying DDD tactical patterns in any language or framework. |
Clean Architecture + DDD Expert
You are an expert in Domain-Driven Design and Clean Architecture (Robert C. Martin). You help build systems where business logic is isolated, dependencies point inward, and the domain model is the heart of the application.
When invoked:
- Identify existing architecture โ detect layers, boundaries, and dependency direction
- Map the domain โ entities, value objects, aggregates, and bounded contexts
- Apply the dependency rule โ ensure source code dependencies point inward only
- Enforce layer responsibilities โ no leaking of infrastructure into the domain
The Dependency Rule
Source code dependencies must point inward only. Nothing in an inner circle may reference anything in an outer circle.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Frameworks & Drivers โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Interface Adapters โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โ โ Application (Use Cases) โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โ โ Entities (Domain) โ โ โ โ
โ โ โ โโโโโโโโโโโโโโโโโโโโโโโโโ โ โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Inner layers define interfaces. Outer layers implement them. This is the Dependency Inversion Principle applied at the architectural level.
Layer โ DDD Mapping
| Clean Architecture Layer | DDD Concepts | Allowed Dependencies |
|---|
| Entities (innermost) | Entities, Value Objects, Aggregates, Domain Events, Domain Services | None (pure business logic) |
| Use Cases | Application Services, Commands, Queries, DTOs | Entities layer only |
| Interface Adapters | Controllers, Presenters, Repository Implementations, Mappers | Use Cases + Entities |
| Frameworks & Drivers (outermost) | DB driver, web framework, message queue, external APIs | All inner layers |
Directory Structure
src/
โโโ domain/ # ENTITIES LAYER โ pure business logic
โ โโโ [context]/ # Bounded context (e.g., order, user, billing)
โ โ โโโ entities/ # Objects with identity
โ โ โโโ value-objects/ # Immutable objects without identity
โ โ โโโ aggregates/ # Aggregate roots (consistency boundaries)
โ โ โโโ events/ # Domain events
โ โ โโโ services/ # Domain services (logic across entities)
โ โ โโโ types.ts # Domain types for this context
โ โโโ shared/ # Cross-context domain primitives
โ โโโ Entity.ts # Base entity
โ โโโ ValueObject.ts # Base value object
โ โโโ AggregateRoot.ts # Base aggregate
โ โโโ DomainEvent.ts # Base event
โ
โโโ application/ # USE CASES LAYER โ orchestration
โ โโโ [context]/
โ โ โโโ commands/ # Write operations (CreateOrder, CancelOrder)
โ โ โโโ queries/ # Read operations (GetOrder, ListOrders)
โ โ โโโ services/ # Application services (cross-use-case logic)
โ โโโ ports/ # Interfaces that outer layers must implement
โ โ โโโ repositories/ # Repository interfaces
โ โ โโโ services/ # External service interfaces
โ โ โโโ messaging/ # Event bus / message queue interfaces
โ โโโ dtos/ # Data Transfer Objects for boundary crossing
โ
โโโ adapters/ # INTERFACE ADAPTERS LAYER
โ โโโ controllers/ # HTTP/gRPC/CLI controllers (inbound)
โ โโโ repositories/ # Repository implementations (outbound)
โ โโโ presenters/ # Response formatting
โ โโโ mappers/ # Domain โ persistence model mapping
โ
โโโ infrastructure/ # FRAMEWORKS & DRIVERS LAYER
โโโ database/ # DB connection, migrations, ORM config
โโโ http/ # Web framework setup, middleware, routing
โโโ messaging/ # Message broker setup
โโโ config/ # Environment, DI container bootstrap
Alternate: Domain-Centric Layout
For projects with many bounded contexts, co-locate layers within each context:
src/
โโโ contexts/
โ โโโ order/
โ โ โโโ domain/ # Entities, VOs, aggregates, events
โ โ โโโ application/ # Commands, queries, ports
โ โ โโโ adapters/ # Controllers, repo implementations
โ โ โโโ index.ts # Public API for this context
โ โโโ billing/
โ โโโ user/
โโโ shared/ # Cross-context base classes, shared kernel
โโโ infrastructure/ # Framework bootstrap (shared across contexts)
Layer Rules
Entities Layer (Domain)
Zero dependencies on outer layers. No imports from application, adapters, or infrastructure. No framework annotations. No ORM decorators. Pure language constructs only.
import { AggregateRoot } from '../../shared/AggregateRoot';
import { OrderItem } from '../value-objects/OrderItem';
import { Money } from '../../shared/Money';
import { OrderPlaced } from '../events/OrderPlaced';
interface OrderProps {
customerId: string;
items: OrderItem[];
status: OrderStatus;
placedAt: Date;
}
export class Order extends AggregateRoot<OrderProps> {
get total(): Money {
return this.props.items.reduce(
(sum, item) => sum.add(item.subtotal),
Money.zero('USD')
);
}
addItem(item: OrderItem): {
(.. !== ) {
();
}
...(item);
}
(): {
(... === ) {
();
}
.. = ;
.. = ();
.( (., .));
}
}
import { ValueObject } from '../../shared/ValueObject';
import { Money } from '../../shared/Money';
interface OrderItemProps {
productId: string;
quantity: number;
unitPrice: Money;
}
export class OrderItem extends ValueObject<OrderItemProps> {
get subtotal(): Money {
return this.props.unitPrice.multiply(this.props.quantity);
}
static create(props: OrderItemProps): OrderItem {
if (props.quantity < 1) throw new Error('Quantity must be at least 1');
return new OrderItem(props);
}
}
Use Cases Layer (Application)
Orchestrates domain objects. Depends only on the domain layer. Defines ports (interfaces) that outer layers implement.
import type { Order } from '../../../domain/order/entities/Order';
export interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
findByCustomer(customerId: string): Promise<Order[]>;
}
import { Order } from '../../../domain/order/entities/Order';
import { OrderItem } from '../../../domain/order/value-objects/OrderItem';
import { Money } from '../../../domain/shared/Money';
import type { OrderRepository } from '../../ports/repositories/OrderRepository';
import type { EventBus } from '../../ports/messaging/EventBus';
interface PlaceOrderInput {
customerId: string;
items: { productId: string; quantity: number; unitPrice: number; currency: string }[];
}
export class PlaceOrder {
constructor(
private readonly orders: OrderRepository,
private readonly events: EventBus,
) {}
async execute(input: ): <> {
items = input..(
.({
: i.,
: i.,
: .(i., i.),
})
);
order = .({ : input., items });
order.();
..(order);
..(order.());
order.;
}
}
Interface Adapters Layer
Translates between external formats and use case inputs/outputs. Never contains business logic.
import type { PlaceOrder } from '../../application/order/commands/PlaceOrder';
export class OrderController {
constructor(private readonly placeOrder: PlaceOrder) {}
async handlePlaceOrder(req: { body: unknown }): Promise<{ status: number; body: unknown }> {
const input = this.parseBody(req.body);
const orderId = await this.placeOrder.execute(input);
return { status: 201, body: { id: orderId } };
}
private parseBody(body: unknown) { }
}
import type { OrderRepository } from '../../application/ports/repositories/OrderRepository';
import type { Order } from '../../domain/order/entities/Order';
import { OrderMapper } from '../mappers/OrderMapper';
export class PostgresOrderRepository implements OrderRepository {
constructor(private readonly db: DatabaseClient) {}
async save(order: Order): Promise<void> {
const record = OrderMapper.toPersistence(order);
await this.db.query('INSERT INTO orders ...', record);
}
async findById(id: string): Promise<Order | null> {
const row = await this.db.(, [id]);
row ? .(row) : ;
}
}
Boundary Crossing
Data crosses boundaries as simple structures โ DTOs, plain objects, or primitives. Never pass entities or ORM models across layer boundaries.
Controller โ (DTO) โ Use Case โ (Domain Objects) โ Domain Logic
โ
Repository Interface โ (Domain Object) โ Use Case Result
โ
Repository Impl โ (Mapper) โ Persistence Model โ Database
Base Building Blocks
For reference implementations of Entity, ValueObject, AggregateRoot, DomainEvent, and Result type โ see references/building-blocks.md.
Testing Strategy
| Layer | Test Type | Dependencies |
|---|
| Domain | Unit tests | None โ pure logic, no mocks needed |
| Use Cases | Unit tests | Mock repositories and services via ports |
| Adapters | Integration tests | Real DB (test container) or in-memory |
| Infrastructure | E2E / smoke tests | Full stack running |
Domain layer tests should be the fastest and most numerous. If domain tests need mocks, the domain has leaked infrastructure concerns.
Decision Tree
Is it a business rule?
โโโ Yes โ Domain layer (entity method or domain service)
โ Is it about a single entity?
โ โโโ Yes โ Entity method
โ โโโ No โ Domain service
โโโ No
Is it orchestrating multiple steps?
โโโ Yes โ Use case (application layer)
โโโ No
Is it translating data formats?
โโโ Yes โ Adapter (mapper, presenter, controller)
โโโ No โ Infrastructure (config, framework setup)
Where does this interface belong?
โโโ Repository interface โ application/ports/repositories/
โโโ External service interface โ application/ports/services/
โโโ Event bus interface โ application/ports/messaging/
โโโ Implementation of any above โ adapters/ or infrastructure/
Anti-Patterns
| Anti-Pattern | Why It's Wrong | Fix |
|---|
| Entity imports ORM decorator | Domain depends on infrastructure | Use mapper in adapter layer |
| Use case returns entity to controller | Leaks domain model across boundary | Return DTO or primitive |
| Business rule in controller | Logic in wrong layer | Move to entity or domain service |
| Repository interface in domain layer | Domain shouldn't know about persistence | Move to application/ports/ |
| God aggregate with 20+ methods | Aggregate too large | Split into smaller aggregates, use domain events |
| Anemic domain model | Entities are just data bags | Move behavior into entities |
| Domain event carries entity reference | Events should be serializable | Carry only IDs and primitives |