ddd
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDomain-Driven Design
领域驱动设计(Domain-Driven Design,DDD)
Strategic DDD
战略DDD
┌─────────────────┐ ┌─────────────────┐
│ Order Context │────▶│ Payment Context │
│ (core domain) │ │ (supporting) │
└────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ Inventory Context│ │ Notification Ctx │
│ (supporting) │ │ (generic) │
└─────────────────┘ └─────────────────┘┌─────────────────┐ ┌─────────────────┐
│ Order Context │────▶│ Payment Context │
│ (core domain) │ │ (supporting) │
└────────┬────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐
│ Inventory Context│ │ Notification Ctx │
│ (supporting) │ │ (generic) │
└─────────────────┘ └─────────────────┘Tactical DDD (TypeScript)
战术DDD(TypeScript实现)
Entity
Entity
typescript
class Order {
private constructor(
readonly id: OrderId,
private items: OrderItem[],
private status: OrderStatus,
private readonly createdAt: Date,
) {}
static create(items: OrderItem[]): Order {
if (items.length === 0) throw new DomainError('Order must have at least one item');
return new Order(OrderId.generate(), items, OrderStatus.PENDING, new Date());
}
get total(): Money {
return this.items.reduce((sum, item) => sum.add(item.subtotal), Money.zero('USD'));
}
confirm(): DomainEvent[] {
if (this.status !== OrderStatus.PENDING) throw new DomainError('Can only confirm pending orders');
this.status = OrderStatus.CONFIRMED;
return [new OrderConfirmed(this.id, this.total)];
}
}typescript
class Order {
private constructor(
readonly id: OrderId,
private items: OrderItem[],
private status: OrderStatus,
private readonly createdAt: Date,
) {}
static create(items: OrderItem[]): Order {
if (items.length === 0) throw new DomainError('Order must have at least one item');
return new Order(OrderId.generate(), items, OrderStatus.PENDING, new Date());
}
get total(): Money {
return this.items.reduce((sum, item) => sum.add(item.subtotal), Money.zero('USD'));
}
confirm(): DomainEvent[] {
if (this.status !== OrderStatus.PENDING) throw new DomainError('Can only confirm pending orders');
this.status = OrderStatus.CONFIRMED;
return [new OrderConfirmed(this.id, this.total)];
}
}Value Object
Value Object
typescript
class Money {
private constructor(readonly amount: number, readonly currency: string) {
if (amount < 0) throw new DomainError('Amount cannot be negative');
}
static of(amount: number, currency: string): Money {
return new Money(Math.round(amount * 100) / 100, currency);
}
static zero(currency: string): Money { return new Money(0, currency); }
add(other: Money): Money {
if (this.currency !== other.currency) throw new DomainError('Currency mismatch');
return Money.of(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
}typescript
class Money {
private constructor(readonly amount: number, readonly currency: string) {
if (amount < 0) throw new DomainError('Amount cannot be negative');
}
static of(amount: number, currency: string): Money {
return new Money(Math.round(amount * 100) / 100, currency);
}
static zero(currency: string): Money { return new Money(0, currency); }
add(other: Money): Money {
if (this.currency !== other.currency) throw new DomainError('Currency mismatch');
return Money.of(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
}Domain Event
Domain Event
typescript
class OrderConfirmed implements DomainEvent {
readonly occurredAt = new Date();
constructor(readonly orderId: OrderId, readonly total: Money) {}
}typescript
class OrderConfirmed implements DomainEvent {
readonly occurredAt = new Date();
constructor(readonly orderId: OrderId, readonly total: Money) {}
}Repository (Port)
Repository (Port)
typescript
interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
save(order: Order): Promise<void>;
nextId(): OrderId;
}typescript
interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
save(order: Order): Promise<void>;
nextId(): OrderId;
}Application Service
Application Service
typescript
class ConfirmOrderUseCase {
constructor(
private orders: OrderRepository,
private eventBus: EventBus,
) {}
async execute(orderId: string): Promise<void> {
const order = await this.orders.findById(OrderId.from(orderId));
if (!order) throw new NotFoundError('Order', orderId);
const events = order.confirm();
await this.orders.save(order);
await this.eventBus.publishAll(events);
}
}typescript
class ConfirmOrderUseCase {
constructor(
private orders: OrderRepository,
private eventBus: EventBus,
) {}
async execute(orderId: string): Promise<void> {
const order = await this.orders.findById(OrderId.from(orderId));
if (!order) throw new NotFoundError('Order', orderId);
const events = order.confirm();
await this.orders.save(order);
await this.eventBus.publishAll(events);
}
}DDD Building Blocks
DDD构建块
| Building Block | Purpose | Identity? | Mutable? |
|---|---|---|---|
| Entity | Domain object with identity | Yes (ID) | Yes |
| Value Object | Immutable descriptor | No (structural equality) | No |
| Aggregate | Consistency boundary | Root entity has ID | Yes (via root) |
| Domain Event | Something that happened | Event ID | No |
| Repository | Persistence abstraction | N/A | N/A |
| Domain Service | Logic not belonging to entity | N/A | N/A |
| 构建块(Building Block) | 用途 | 是否有标识? | 是否可变? |
|---|---|---|---|
| Entity | 带唯一标识的领域对象 | 是(ID) | 是 |
| Value Object | 不可变描述对象 | 否(基于结构相等) | 否 |
| Aggregate | 一致性边界 | 根实体拥有ID | 是(通过根实体) |
| Domain Event | 领域中发生的事件 | 事件ID | 否 |
| Repository | 持久化抽象层 | 不适用 | 不适用 |
| Domain Service | 不属于实体的业务逻辑 | 不适用 | 不适用 |
Anti-Patterns
反模式
| Anti-Pattern | Fix |
|---|---|
| Anemic domain model (logic in services) | Put business logic in entities |
| Aggregate too large | Keep aggregates small, reference by ID |
| Exposing entity internals | Use methods that express domain intent |
| Cross-aggregate transactions | Use domain events for eventual consistency |
| Repository returning DTOs | Return domain objects, map in application layer |
| 反模式 | 修复方案 |
|---|---|
| 贫血领域模型(业务逻辑在服务中) | 将业务逻辑迁移至实体中 |
| 聚合过大 | 保持聚合粒度较小,通过ID引用其他聚合 |
| 暴露实体内部细节 | 使用表达领域意图的方法 |
| 跨聚合事务 | 使用领域事件实现最终一致性 |
| 仓库返回DTO | 返回领域对象,在应用层进行映射 |
Production Checklist
生产环境检查清单
- Bounded contexts identified and documented
- Ubiquitous language in code matches business terms
- Aggregates enforce invariants
- Value objects for all descriptors (Money, Email, Address)
- Domain events for cross-context communication
- Repository pattern for persistence abstraction
- 已识别并记录Bounded Context
- 代码中的通用语言(Ubiquitous Language)与业务术语一致
- 聚合能够强制执行业务不变量
- 所有描述性类型均使用Value Object(如Money、Email、Address)
- 使用Domain Event实现跨上下文通信
- 使用Repository模式作为持久化抽象层