前端领域驱动设计:构建业务聚焦的应用架构
前端领域驱动设计:构建业务聚焦的应用架构
前言
嘿,各位前端小伙伴!今天我们来聊聊领域驱动设计(Domain-Driven Design,简称DDD)。如果你之前主要关注UI层的开发,那么DDD可能会为你打开一扇新的大门。
领域驱动设计的核心思想是:将业务领域作为软件设计的核心。想象一下,你的应用就像一座城市,业务领域就是城市的规划蓝图。有了这个蓝图,各个组件就能和谐协作,共同实现业务目标。
一、领域驱动设计基础
1.1 核心概念
DDD包含几个核心概念:
- 领域(Domain):业务所涉及的范围和知识
- 领域模型(Domain Model):对领域的抽象表示
- 实体(Entity):具有唯一标识的业务对象
- 值对象(Value Object):描述性的对象,没有唯一标识
- 聚合(Aggregate):一组相关对象的组合
- 聚合根(Aggregate Root):聚合的入口点
// 实体示例:用户 interface User { id: string; // 唯一标识 name: string; email: string; createdAt: Date; } // 值对象示例:地址 interface Address { street: string; city: string; postalCode: string; country: string; } // 值对象应该是不可变的 class Money { constructor(public readonly amount: number, public readonly currency: string) {} add(other: Money): Money { if (this.currency !== other.currency) { throw new Error('货币类型不匹配'); } return new Money(this.amount + other.amount, this.currency); } }1.2 聚合与聚合根
interface OrderItem { productId: string; quantity: number; price: Money; } interface Order { id: string; userId: string; items: OrderItem[]; total: Money; status: 'pending' | 'paid' | 'shipped' | 'delivered'; createdAt: Date; } class OrderAggregate { private order: Order; constructor(order: Order) { this.order = order; } get id(): string { return this.order.id; } addItem(item: OrderItem): void { if (this.order.status !== 'pending') { throw new Error('只能向待处理订单添加商品'); } this.order.items.push(item); this.updateTotal(); } removeItem(productId: string): void { if (this.order.status !== 'pending') { throw new Error('只能从待处理订单移除商品'); } this.order.items = this.order.items.filter(item => item.productId !== productId); this.updateTotal(); } pay(): void { if (this.order.status !== 'pending') { throw new Error('只能支付待处理订单'); } if (this.order.items.length === 0) { throw new Error('订单没有商品'); } this.order.status = 'paid'; } private updateTotal(): void { this.order.total = this.order.items.reduce((sum, item) => sum.add(item.price), new Money(0, 'CNY') ); } }二、领域服务
2.1 业务逻辑封装
interface OrderRepository { save(order: Order): Promise<void>; findById(id: string): Promise<Order | null>; findByUserId(userId: string): Promise<Order[]>; } interface PaymentService { processPayment(orderId: string, amount: Money): Promise<boolean>; } class OrderService { constructor( private orderRepository: OrderRepository, private paymentService: PaymentService ) {} async createOrder(userId: string, items: OrderItem[]): Promise<OrderAggregate> { const order: Order = { id: crypto.randomUUID(), userId, items: [], total: new Money(0, 'CNY'), status: 'pending', createdAt: new Date() }; const aggregate = new OrderAggregate(order); items.forEach(item => aggregate.addItem(item)); await this.orderRepository.save(aggregate['order']); return aggregate; } async payOrder(orderId: string): Promise<void> { const order = await this.orderRepository.findById(orderId); if (!order) { throw new Error('订单不存在'); } const aggregate = new OrderAggregate(order); const success = await this.paymentService.processPayment( orderId, aggregate['order'].total ); if (success) { aggregate.pay(); await this.orderRepository.save(aggregate['order']); } } }三、领域事件
3.1 事件驱动的业务流程
interface DomainEvent { eventId: string; aggregateId: string; eventType: string; timestamp: Date; } class OrderCreatedEvent implements DomainEvent { eventId: string; timestamp: Date; constructor( public readonly aggregateId: string, public readonly userId: string, public readonly items: OrderItem[] ) { this.eventId = crypto.randomUUID(); this.timestamp = new Date(); this.eventType = 'OrderCreated'; } } class OrderPaidEvent implements DomainEvent { eventId: string; timestamp: Date; constructor(public readonly aggregateId: string) { this.eventId = crypto.randomUUID(); this.timestamp = new Date(); this.eventType = 'OrderPaid'; } } interface DomainEventPublisher { publish(event: DomainEvent): void; } class OrderService { constructor( private orderRepository: OrderRepository, private paymentService: PaymentService, private eventPublisher: DomainEventPublisher ) {} async createOrder(userId: string, items: OrderItem[]): Promise<OrderAggregate> { // ... 创建订单逻辑 // 发布领域事件 this.eventPublisher.publish(new OrderCreatedEvent(order.id, userId, items)); return aggregate; } async payOrder(orderId: string): Promise<void> { // ... 支付逻辑 // 发布领域事件 this.eventPublisher.publish(new OrderPaidEvent(orderId)); } }四、应用层
4.1 用例编排
interface CreateOrderInput { userId: string; items: Array<{ productId: string; quantity: number; price: number }>; } interface CreateOrderOutput { orderId: string; total: number; status: string; } class CreateOrderUseCase { constructor(private orderService: OrderService) {} async execute(input: CreateOrderInput): Promise<CreateOrderOutput> { const orderItems: OrderItem[] = input.items.map(item => ({ productId: item.productId, quantity: item.quantity, price: new Money(item.price, 'CNY') })); const order = await this.orderService.createOrder(input.userId, orderItems); return { orderId: order.id, total: order['order'].total.amount, status: order['order'].status }; } }五、基础设施层
5.1 持久化实现
class DatabaseOrderRepository implements OrderRepository { async save(order: Order): Promise<void> { await fetch('/api/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(order) }); } async findById(id: string): Promise<Order | null> { const response = await fetch(`/api/orders/${id}`); return response.ok ? response.json() : null; } async findByUserId(userId: string): Promise<Order[]> { const response = await fetch(`/api/orders?userId=${userId}`); return response.json(); } } class StripePaymentService implements PaymentService { async processPayment(orderId: string, amount: Money): Promise<boolean> { const response = await fetch('/api/payments/stripe', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ orderId, amount: amount.amount, currency: amount.currency }) }); const result = await response.json(); return result.success; } }六、UI层集成
6.1 React组件示例
interface OrderFormProps { onSubmit: (items: Array<{ productId: string; quantity: number; price: number }>) => Promise<void>; } export function OrderForm({ onSubmit }: OrderFormProps) { const [items, setItems] = useState<Array<{ productId: string; quantity: number; price: number }>>([]); const [productId, setProductId] = useState(''); const [quantity, setQuantity] = useState(1); const [price, setPrice] = useState(0); const handleAddItem = () => { if (!productId || price <= 0) return; setItems([...items, { productId, quantity, price }]); setProductId(''); setQuantity(1); setPrice(0); }; const handleRemoveItem = (index: number) => { setItems(items.filter((_, i) => i !== index)); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (items.length === 0) return; try { await onSubmit(items); setItems([]); } catch (error) { console.error('创建订单失败:', error); } }; const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0); return ( <form onSubmit={handleSubmit}> <div> <input type="text" placeholder="商品ID" value={productId} onChange={(e) => setProductId(e.target.value)} /> <input type="number" placeholder="数量" value={quantity} onChange={(e) => setQuantity(parseInt(e.target.value) || 1)} /> <input type="number" placeholder="价格" value={price} onChange={(e) => setPrice(parseFloat(e.target.value) || 0)} /> <button type="button" onClick={handleAddItem}>添加商品</button> </div> <div> <h3>订单商品</h3> {items.map((item, index) => ( <div key={index}> <span>{item.productId} x {item.quantity} = {item.price * item.quantity}</span> <button type="button" onClick={() => handleRemoveItem(index)}>删除</button> </div> ))} </div> <div> <strong>总计: {total}</strong> </div> <button type="submit" disabled={items.length === 0}>创建订单</button> </form> ); }6.2 使用案例
// 依赖注入 const orderRepository = new DatabaseOrderRepository(); const paymentService = new StripePaymentService(); const eventPublisher = new InMemoryEventPublisher(); const orderService = new OrderService(orderRepository, paymentService, eventPublisher); const createOrderUseCase = new CreateOrderUseCase(orderService); // UI组件 function OrderPage() { const handleCreateOrder = async (items: Array<{ productId: string; quantity: number; price: number }>) => { const result = await createOrderUseCase.execute({ userId: 'current-user-id', items }); console.log('订单创建成功:', result); }; return ( <div> <h1>创建订单</h1> <OrderForm onSubmit={handleCreateOrder} /> </div> ); }七、DDD优势
7.1 业务聚焦
DDD将业务逻辑从技术细节中分离出来:
// 业务规则在领域层 class OrderAggregate { pay(): void { if (this.order.status !== 'pending') { throw new Error('只能支付待处理订单'); } // ... } } // 技术实现在基础设施层 class DatabaseOrderRepository implements OrderRepository { async save(order: Order): Promise<void> { // 数据库操作 } }7.2 可测试性
describe('OrderService', () => { it('should create an order', async () => { const mockRepo: OrderRepository = { save: jest.fn().mockResolvedValue(), findById: jest.fn().mockResolvedValue(null), findByUserId: jest.fn().mockResolvedValue([]) }; const mockPaymentService: PaymentService = { processPayment: jest.fn().mockResolvedValue(true) }; const mockPublisher: DomainEventPublisher = { publish: jest.fn() }; const service = new OrderService(mockRepo, mockPaymentService, mockPublisher); const order = await service.createOrder('user1', [ { productId: 'p1', quantity: 2, price: new Money(100, 'CNY') } ]); expect(mockRepo.save).toHaveBeenCalled(); expect(mockPublisher.publish).toHaveBeenCalledWith(expect.any(OrderCreatedEvent)); }); });7.3 可维护性
业务规则集中在领域层,易于理解和修改:
// 如果业务规则改变,只需修改领域层 class OrderAggregate { // 新增规则:订单金额超过1000元需要审核 pay(): void { if (this.order.status !== 'pending') { throw new Error('只能支付待处理订单'); } if (this.order.total.amount > 1000) { this.order.status = 'pending_review'; } else { this.order.status = 'paid'; } } }八、总结
领域驱动设计为前端应用提供了一种业务聚焦的架构方式:
- 领域层:包含实体、值对象、聚合和业务规则
- 应用层:编排用例,协调领域层和基础设施层
- 基础设施层:实现持久化、外部服务调用等技术细节
- UI层:展示数据和接收用户输入
通过将业务逻辑从技术细节中分离出来,DDD帮助我们构建:
- 业务聚焦:代码直接反映业务规则
- 可测试:各层可以独立测试
- 可维护:业务逻辑集中,易于理解和修改
- 可扩展:添加新功能只需在相应层添加代码
如果你正在开发一个业务复杂的前端应用,DDD是一个值得考虑的架构选择!
延伸阅读
- Domain-Driven Design by Eric Evans
- Implementing Domain-Driven Design by Vaughn Vernon
- DDD in TypeScript
如果你喜欢这篇文章,请点赞、收藏、关注三连!你的支持是我创作的最大动力!🚀
