昨天我們完成了抽象建模的四重境界,從概念、行為、資料、架構四個維度理解了系統的本質。但當我們要將這些抽象理解轉化為可執行的業務邏輯時,面臨一個關鍵挑戰:
如何將領域概念組織成內聚且有意義的業務單元?
這就是DDD實踐的核心問題:從用例分析到聚合設計的系統化轉換。今天我們將建立領域模型的靜態結構,為明天深入探討使用者操作情境做好基礎。
在Day 2我們分析了六個典型用戶案例,每個案例背後都有其核心用例模式。讓我們從DDD的角度重新解構:
投資交易系統 [I-N-T-J] - 控制型用例模式
核心用例:執行即時交易決策
Primary Actor: 專業交易者
Goal in Context: 在最短時間內完成高質量交易
Scope: 個人投資組合管理系統
Level: 用戶目標級別
主要成功場景:
1. 交易者監控市場數據
2. 識別交易機會
3. 計算風險影響
4. 提交交易指令
5. 確認交易執行
6. 更新投資組合狀態
延伸場景:
2a. 市場數據延遲 → 系統提供延遲警告
3a. 風險超過限額 → 系統要求明確確認
4a. 資金不足 → 系統建議調整或融資
5a. 交易失敗 → 系統進入重試或人工處理
從這個用例分析中,我們可以識別出幾個核心領域概念:
家庭財務系統 [E-N-T-J] - 協調型用例模式
核心用例:管理家庭協作支出
Primary Actor: 家庭財務負責人
Goal in Context: 協調家庭成員的支出行為,維持預算平衡
Scope: 家庭財務管理系統
Level: 業務摘要級別
主要成功場景:
1. 設定家庭預算與權限
2. 家庭成員記錄支出
3. 系統檢查預算狀況
4. 觸發預算警告或調整
5. 生成家庭財務報告
角色交互複雜度:
- 主要決策者:設定規則,監控整體
- 一般成員:執行支出,遵守規則
- 系統協調:確保規則執行,提供透明度
領域概念識別:
通過六個案例的對比分析,我們發現不同的用例模式對應不同的領域複雜度特徵:
用例模式 | 主要複雜度 | 核心挑戰 | 設計重點 |
---|---|---|---|
控制型 (投資交易) | 時間敏感性 | 毫秒級決策 | 狀態一致性 |
協調型 (家庭財務) | 多角色協作 | 權限與透明度 | 角色邊界 |
確定型 (健康監控) | 數據可靠性 | 連續性監控 | 數據完整性 |
適應型 (智慧家居) | 彈性響應 | 漸進式控制 | 優雅降級 |
透明型 (包裹追蹤) | 信息同步 | 多源數據整合 | 狀態同步 |
創造型 (旅遊分享) | 表達靈活性 | 創意支持工具 | 內容流動性 |
每種模式都會影響我們的聚合設計策略。
原則1:業務不變式邊界
聚合的邊界應該對應業務中必須保持一致性的概念集合。
投資交易系統的不變式分析:
Portfolio聚合的不變式:
- 總資產價值 = Σ(持倉價值) + 現金餘額
- 任何交易不能導致資金為負
- 風險敞口不能超過預設限額
Order聚合的不變式:
- 訂單一旦提交不能修改核心參數
- 執行記錄必須與原始指令對應
- 狀態轉換必須符合業務流程
原則2:事務邊界對應
一個聚合內的修改應該能在單一事務中完成。
家庭財務系統的事務分析:
Family聚合:
✓ 可在單一事務中完成:
- 設定預算額度
- 調整成員權限
- 更新家庭基本信息
✗ 不能在單一事務中完成:
- 統計所有成員的月度支出 (跨多個聚合)
- 生成年度報告 (數據量太大)
原則3:業務語言對應
聚合的命名和結構應該直接對應業務專家的語言。
案例1:投資交易系統
聚合設計:
┌─────────────────┐ ┌─────────────────┐
│ Portfolio │ │ Order │
│ (投資組合) │ │ (交易訂單) │
├─────────────────┤ ├─────────────────┤
│ - portfolioId │ │ - orderId │
│ - traderId │ │ - portfolioId │
│ - holdings[] │ │ - tradeOrder │
│ - cashBalance │ │ - status │
│ - riskLimit │ │ - execution[] │
└─────────────────┘ └─────────────────┘
│ │
└───────────────────────┘
領域事件通信
設計邏輯:
- Portfolio管理資產狀態和風險控制
- Order管理交易流程和執行追蹤
- 兩者通過事件保持最終一致性
案例2:家庭財務系統
聚合設計:
┌─────────────────┐ ┌─────────────────┐
│ Family │ │ Budget │
│ (家庭) │ │ (預算) │
├─────────────────┤ ├─────────────────┤
│ - familyId │ │ - budgetId │
│ - members[] │ │ - familyId │
│ - permissions │ │ - categories[] │
│ - settings │ │ - limits │
└─────────────────┘ │ - period │
└─────────────────┘
│ │
└─────────────────┘
事件驅動協調
┌─────────────────┐
│ Expense │
│ (支出記錄) │
├─────────────────┤
│ - expenseId │
│ - familyId │
│ - memberId │
│ - amount │
│ - category │
│ - timestamp │
└─────────────────┘
設計邏輯:
- Family管理成員和權限結構
- Budget管理預算規則和限額
- Expense記錄實際支出事實
- 三者協作實現預算控制
案例3:健康監控系統
聚合設計:
┌─────────────────┐ ┌─────────────────┐
│ HealthProfile │ │ DeviceReading │
│ (健康檔案) │ │ (設備數據) │
├─────────────────┤ ├─────────────────┤
│ - userId │ │ - readingId │
│ - metrics[] │ │ - userId │
│ - thresholds │ │ - deviceType │
│ - history │ │ - value │
└─────────────────┘ │ - timestamp │
│ - status │
└─────────────────┘
設計邏輯:
- HealthProfile管理長期健康狀態
- DeviceReading管理即時數據收集
- 分離設計支持不同的數據生命週期
不同的領域會產生不同的聚合交互模式:
模式1:事件驅動協調 (投資交易)
Portfolio聚合 → TradeValidatedEvent → Order聚合
Order聚合 → OrderExecutedEvent → Portfolio聚合
Portfolio聚合 → PortfolioUpdatedEvent → RiskManagement聚合
模式2:權限驅動協作 (家庭財務)
Family聚合 → 設定權限規則 → Expense聚合檢查權限
Budget聚合 → 設定限額規則 → Expense聚合檢查限額
Expense聚合 → 記錄支出 → Budget聚合更新狀態
模式3:數據驅動聚合 (健康監控)
Device → 推送數據 → DeviceReading聚合
DeviceReading聚合 → 數據驗證完成 → HealthProfile聚合
HealthProfile聚合 → 閾值檢查 → Alert聚合
當業務邏輯不自然地屬於任何單一聚合時,我們需要領域服務。
投資交易系統的領域服務案例:
// 風險計算服務:需要Portfolio和MarketData的協作
class RiskCalculationService {
calculate(portfolio: Portfolio, marketData: MarketData): RiskMetric {
// 這個邏輯不屬於Portfolio(因為需要外部市場數據)
// 也不屬於MarketData(因為需要組合特定信息)
const portfolioValue = portfolio.getTotalValue(marketData);
const volatility = marketData.calculateVolatility(portfolio.getSymbols());
const concentration = portfolio.calculateConcentration();
return new RiskMetric(portfolioValue, volatility, concentration);
}
}
// 交易執行協調服務:協調Portfolio和Order聚合
class TradeExecutionService {
async executeTrade(
portfolio: Portfolio,
tradeOrder: TradeOrder
): Promise<ExecutionResult> {
// 1. 驗證Portfolio的交易能力
const validation = portfolio.validateTrade(tradeOrder);
if (!validation.isValid) {
return ExecutionResult.rejected(validation.reason);
}
// 2. 創建Order聚合管理執行過程
const order = Order.create(portfolio.getId(), tradeOrder);
// 3. 提交到市場(外部服務)
const marketResult = await this.marketService.submit(order);
// 4. 更新相關聚合狀態
if (marketResult.isSuccessful) {
portfolio.applyExecution(marketResult);
order.markAsExecuted(marketResult);
}
return ExecutionResult.fromMarket(marketResult);
}
}
無狀態性:領域服務本身不維護狀態,只協調聚合間的交互
業務導向:服務的命名和接口應該反映業務概念,而非技術實現
最小職責:每個服務只處理一個明確的業務場景
今天建立的聚合設計,將直接影響明天我們設計使用者操作情境:
角色與聚合的對應關係:
投資交易系統:
- 交易者角色 → Portfolio聚合的所有者
- 系統管理員 → 整體系統的監控者
- 風控專員 → RiskManagement聚合的操作者
家庭財務系統:
- 家庭負責人 → Family聚合的管理者
- 家庭成員 → Expense聚合的創建者
- 預算管理員 → Budget聚合的維護者
操作權限的聚合邊界:
每個聚合的狀態變化,都會產生對應的使用者操作場景:
Portfolio聚合的狀態轉換:
空組合 → [初始化資金] → 活躍組合
活躍組合 → [執行交易] → 活躍組合 (狀態更新)
活躍組合 → [風險超限] → 受限組合
受限組合 → [風險調整] → 活躍組合
活躍組合 → [全部賣出] → 空組合
每個狀態轉換都對應一個使用者操作情境,明天我們將深入探討如何將這些轉換設計成直觀的User Story。
在進行聚合設計時,我們做出了許多重要決策。記錄這些決策對後續的User Story設計至關重要:
決策1:Portfolio和Order分離
決策2:Family聚合包含權限管理
決策3:健康數據採用讀寫分離
通過今天的聚合設計,我們建立了業務邏輯的靜態結構。明天我們將深入探討:
記住:我們今天建立的不是技術架構,而是業務邏輯的組織方式。這個組織方式將直接決定明天我們如何設計使用者的操作體驗。
「聚合不是代碼的分組,而是業務概念的生存單位。每個聚合都有其獨特的生命週期,而使用者的每個操作都是在參與這個生命週期的演進。」