领域驱动设计:实战指南
DDD 让你的代码结构与它所建模的业务领域保持一致。
核心构建块
值对象
由属性定义的不可变对象。
class Money {
private constructor(
private readonly amount: number,
private readonly currency: string
) {
if (amount < 0) throw new Error('Amount cannot be negative');
if (!['USD', 'EUR', 'GBP'].includes(currency)) {
throw new Error('Invalid currency');
}
}
static of(amount: number, currency: string): Money {
return new Money(amount, currency);
}
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;
}
toString() { return `${this.amount} ${this.currency}`; }
}
class Email {
private constructor(private readonly value: string) {}
static of(email: string): Email {
if (!/^[^@]+@[^@]+\.[^@]+$/.test(email)) {
throw new Error('Invalid email format');
}
return new Email(email.toLowerCase());
}
toString() { return this.value; }
}
实体
具有身份且随时间持续存在的对象。
abstract class Entity<T> {
constructor(protected readonly id: T) {}
equals(other: Entity<T>): boolean {
return this.id === other.id;
}
}
class UserId {
constructor(readonly value: string) {}
}
class User extends Entity<UserId> {
private email: Email;
private name: string;
private constructor(id: UserId, email: Email, name: string) {
super(id);
this.email = email;
this.name = name;
}
static create(email: string, name: string): User {
const id = new UserId(crypto.randomUUID());
return new User(id, Email.of(email), name);
}
changeEmail(newEmail: string): void {
this.email = Email.of(newEmail);
}
}
聚合
以根实体为核心的实体集群,用于维护不变量。
class Order extends Entity<OrderId> {
private items: OrderItem[] = [];
private status: OrderStatus = OrderStatus.PENDING;
addItem(productId: ProductId, quantity: number, price: Money): void {
if (this.status !== OrderStatus.PENDING) {
throw new Error('Cannot add items to non-pending order');
}
const existing = this.items.find(i => i.productId.equals(productId));
if (existing) {
existing.increaseQuantity(quantity);
} else {
this.items.push(new OrderItem(productId, quantity, price));
}
}
confirm(): void {
if (this.items.length === 0) {
throw new Error('Cannot confirm empty order');
}
this.status = OrderStatus.CONFIRMED;
}
get total(): Money {
return this.items.reduce(
(sum, item) => sum.add(item.subtotal),
Money.of(0, 'USD')
);
}
}
仓储
interface OrderRepository {
findById(id: OrderId): Promise<Order | null>;
findByUserId(userId: UserId): Promise<Order[]>;
save(order: Order): Promise<void>;
delete(id: OrderId): Promise<void>;
}
class PostgresOrderRepository implements OrderRepository {
constructor(private db: Database) {}
async findById(id: OrderId): Promise<Order | null> {
const row = await this.db.query(
'SELECT * FROM orders WHERE id = $1',
[id.value]
);
return row ? OrderMapper.toDomain(row) : null;
}
async save(order: Order): Promise<void> {
const data = OrderMapper.toPersistence(order);
await this.db.query(
'INSERT INTO orders ... ON CONFLICT (id) DO UPDATE ...',
[data]
);
}
}
领域服务
class OrderPricingService {
constructor(
private readonly discountRepository: DiscountRepository,
private readonly taxService: TaxService
) {}
async calculateTotal(order: Order, userId: UserId): Promise<Money> {
const subtotal = order.subtotal;
const discount = await this.discountRepository.findForUser(userId);
const discounted = discount ? discount.apply(subtotal) : subtotal;
const tax = await this.taxService.calculate(discounted);
return discounted.add(tax);
}
}
领域事件
interface DomainEvent {
readonly occurredAt: Date;
readonly eventType: string;
}
class OrderConfirmed implements DomainEvent {
readonly occurredAt = new Date();
readonly eventType = 'OrderConfirmed';
constructor(
readonly orderId: string,
readonly userId: string,
readonly total: Money
) {}
}
DDD 在复杂领域中回报丰厚,尤其是当业务规则成为竞争优势时。