複雜的業務,事件風暴驅動 DDD 也許是良好的解決方案
在微服務架構大行其道的今天,如何有效處理複雜業務系統的領域邊界劃分始終是一個難題。
事件風暴作爲領域驅動設計 (DDD) 中的一項核心實踐,它通過業務部門、產品、開發等多方協作的工作坊形式,幫助團隊釐清業務流程、統一認知,從而更好地指導微服務架構設計。
- 事件風暴的本質認知
傳統的需求分析往往陷入細節泥潭,而事件風暴則轉換視角,以業務事件爲核心,構建起完整的業務場景圖景。
1.1 核心要素
-
• 領域事件:用橙色便籤表示,採用 "過去時" 描述,如 "訂單已支付"
-
• 命令:用藍色便籤表示,觸發事件的操作,如 "提交訂單"
-
• 外部系統:用紫色便籤表示,與當前系統存在交互的外部依賴
-
• 聚合根:用黃色便籤表示,用於組織和管理一組相關實體
-
• 策略 / 規則:用綠色便籤表示,描述重要的業務規則與約束
- 事件風暴工作坊實踐精要
2.1 前期準備
-
• 參與人員:產品負責人、領域專家、架構師、核心開發
-
• 物料準備:大尺寸白板、不同顏色便籤、馬克筆
-
• 時間安排:建議 2-3 天,每天 6 小時,保持專注
2.2 探索階段
用一個訂單場景舉例:
-
- 收集領域事件:"訂單已創建"→"支付已完成"→"庫存已鎖定"→"訂單已發貨"
-
- 分析觸發命令:"提交訂單"→"支付訂單"→"確認發貨"
-
- 識別外部系統:支付系統、庫存系統、物流系統
-
- 討論業務規則:庫存不足時訂單創建策略、訂單超時關閉規則
-
從事件風暴到領域模型
3.1 領域分割技巧
-
• 業務行爲內聚:將緊密關聯的業務行爲歸入同一領域
-
• 數據依賴分析:梳理實體間的數據依賴關係
-
• 變更頻率考量:變更頻率相近的功能適合放在一起
舉個案例:電商訂單域的劃分
訂單域:
- 聚合根:訂單(Order)
- 實體:訂單項(OrderItem)、收貨地址(ShippingAddress)
- 值對象:商品快照(ProductSnapshot)、支付信息(PaymentInfo)
3.2 上下文映射
-
• 合作關係 (Partnership):訂單域與支付域
-
• 防腐層 (ACL):訂單域與外部物流系統
-
• 開放主機服務 (OHS):訂單域對外提供的 API
-
• 發佈語言 (Published Language):統一的消息格式規範
- 實現層面的考量
4.1 領域事件的異步處理
@DomainEvents
Collection<OrderEvent> domainEvents() {
// 收集待發布的領域事件
return Collections.unmodifiableCollection(events);
}
@Async
@EventListener
public void handleOrderPaidEvent(OrderPaidEvent event) {
// 處理訂單支付完成事件
inventoryService.lockStock(event.getOrderId());
}
4.2 聚合根設計
@Aggregate
public class Order {
@AggregateIdentifier
private OrderId id;
private OrderStatus status;
private Money totalAmount;
private List<OrderItem> items;
@CommandHandler
public Order(CreateOrderCommand cmd) {
// 業務規則驗證
validateBusinessRules(cmd);
// 應用領域事件
apply(new OrderCreatedEvent(cmd.getOrderId(), cmd.getItems()));
}
}
- 注意事項與最佳實踐
5.1 常見陷阱
-
• 過度細化:不必追求完美,把握主要矛盾
-
• 忽視約束:要充分考慮業務規則和技術約束
-
• 角色缺失:確保關鍵角色參與,特別是領域專家
5.2 成功要素
-
• 統一語言:建立領域通用語言詞彙表
-
• 持續演進:領域模型需要隨業務變化不斷調整
-
• 團隊共識:通過工作坊凝聚團隊認知
通過事件風暴,我們不僅能夠快速理清業務脈絡,更重要的是能夠建立起團隊共同的業務認知,這正是 DDD 實踐成功的關鍵所在。
- 進階:事件溯源
事件溯源是 DDD 的一種高級實踐,它通過記錄所有改變狀態的領域事件來重建聚合根狀態。
@EventSourcingHandler
public void on(OrderCreatedEvent event) {
this.id = event.getOrderId();
this.status = OrderStatus.CREATED;
this.items = event.getOrderItems();
}
@EventSourcingHandler
public void on(OrderPaidEvent event) {
this.status = OrderStatus.PAID;
this.paymentInfo = event.getPaymentInfo();
}
這種方式特別適合需要審計、追溯和回滾能力的業務場景。
- DDD 戰術設計模式精講
7.1 值對象 (Value Object) 設計
值對象是領域驅動設計中一個常被誤用的概念,讓我們通過一個實際案例深入理解:
public class Money {
private final BigDecimal amount;
private final Currency currency;
// 值對象特徵:不可變性
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new CurrencyMismatchException();
}
return new Money(amount.add(other.amount), currency);
}
// 值對象特徵:基於屬性的相等性
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount.compareTo(money.amount) == 0 &&
currency.equals(money.currency);
}
}
7.2 聚合設計的進階技巧
7.2.1 一致性邊界控制
舉個訂單場景的聚合設計:
public class Order {
private List<OrderItem> items; // 訂單項作爲訂單聚合的一部分
private OrderStatus status;
private Money totalAmount;
public void addItem(Product product, int quantity) {
// 業務規則驗證
validateProductAvailable(product);
validateOrderEditable();
// 確保聚合內一致性
OrderItem item = new OrderItem(product, quantity);
items.add(item);
recalculateTotalAmount();
}
private void recalculateTotalAmount() {
this.totalAmount = items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
}
7.3 領域服務的高級應用
當某個業務操作涉及多個聚合時,應該使用領域服務:
@DomainService
public class OrderProcessingService {
@Transactional
public void processOrder(Order order) {
// 庫存檢查
inventoryService.checkAndReserve(order.getItems());
// 支付處理
Payment payment = paymentService.process(order.getTotalAmount());
// 訂單狀態更新
order.markAsPaid(payment);
// 發佈領域事件
eventPublisher.publish(new OrderProcessedEvent(order.getId()));
}
}
- 微服務架構與 DDD 的協奏
8.1 限界上下文映射模式
// 防腐層示例
@AntiCorruptionLayer
public class LogisticsServiceAdapter implements LogisticsService {
private final ThirdPartyLogisticsApi api;
@Override
public ShipmentStatus translateStatus(String externalStatus) {
// 將外部系統狀態映射爲我們的領域概念
return switch (externalStatus) {
case "SHIPPED" -> ShipmentStatus.IN_TRANSIT;
case "DELIVERED" -> ShipmentStatus.DELIVERED;
default -> ShipmentStatus.UNKNOWN;
};
}
}
8.2 分佈式事務處理
基於 DDD 的事件驅動架構處理分佈式事務:
@Saga
public class OrderFulfillmentSaga {
@StartSaga
@SagaEventHandler(associationProperty = "orderId")
public void handle(OrderCreatedEvent event) {
// 發送支付命令
commandGateway.send(new ProcessPaymentCommand(event.getOrderId()));
}
@SagaEventHandler(associationProperty = "orderId")
public void handle(PaymentCompletedEvent event) {
// 發送庫存鎖定命令
commandGateway.send(new ReserveInventoryCommand(event.getOrderId()));
}
@EndSaga
@SagaEventHandler(associationProperty = "orderId")
public void handle(InventoryReservedEvent event) {
// Saga結束
// 發送訂單確認命令
commandGateway.send(new ConfirmOrderCommand(event.getOrderId()));
}
}
- DDD 實踐的演進與優化
9.1 性能優化策略
9.1.1 聚合加載優化
@Repository
public class OrderRepository {
// 針對不同場景的加載策略
public Order findByIdWithItems(OrderId id) {
return entityManager
.createQuery("""
SELECT o FROM Order o
LEFT JOIN FETCH o.items
WHERE o.id = :id
""")
.setParameter("id", id)
.getSingleResult();
}
// 僅加載訂單狀態的輕量級查詢
public OrderStatus findOrderStatusById(OrderId id) {
return entityManager
.createQuery("""
SELECT o.status FROM Order o
WHERE o.id = :id
""")
.setParameter("id", id)
.getSingleResult();
}
}
9.2 CQRS 模式應用
將複雜查詢與命令處理分離:
// 命令側
@CommandHandler
public class OrderCommandHandler {
public void handle(CreateOrderCommand cmd) {
Order order = new Order(cmd.getOrderId(), cmd.getItems());
repository.save(order);
}
}
// 查詢側
@QueryHandler
public class OrderQueryHandler {
public OrderSummaryDTO handle(GetOrderSummaryQuery query) {
return orderReadRepository
.findOrderSummaryById(query.getOrderId())
.orElseThrow(() -> new OrderNotFoundException(query.getOrderId()));
}
}
- 總結與展望
DDD 結合事件風暴不僅是一種設計方法,更是一種促進團隊協作、統一認知的有效工具。
在文章最後添加以下參考資料段落:
參考資料
-
- Vernon V. (2023). Implementing Domain-Driven Design. Addison-Wesley Professional.
這本書深入探討了 DDD 的戰術設計模式和實現策略
- Vernon V. (2023). Implementing Domain-Driven Design. Addison-Wesley Professional.
-
- Evans E. (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley Professional.
DDD 的奠基之作,提出了核心概念和方法論
- Evans E. (2003). Domain-Driven Design: Tackling Complexity in the Heart of Software. Addison-Wesley Professional.
-
- Brandolini A. (2021). Event Storming: An act of Deliberate Collective Learning.
事件風暴方法的創始人所著,詳細闡述了工作坊的組織和引導技巧
- Brandolini A. (2021). Event Storming: An act of Deliberate Collective Learning.
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/HSFvxmDTAi8twvCaY_Sxuw