Domain-Driven Design DDD 領域驅動開發筆記

【核心知識】
DDD 是一種軟體開發方法論,透過深入理解業務領域知識,將複雜的業務邏輯轉化為清晰的程式碼結構,讓技術實作與業務需求緊密對齊。


【三維解析】

原理層:核心運作機制

DDD 的核心理念是「讓程式碼說業務的語言」,主要透過以下機制運作:

  1. 通用語言 (Ubiquitous Language):開發團隊與領域專家共同建立一套統一的術語系統,消除溝通鴻溝
  2. 限界上下文 (Bounded Context):將大型系統切分成多個獨立的業務邊界,每個邊界內有自己的模型與規則
  3. 分層架構
    • 領域層 (Domain Layer):核心業務邏輯
    • 應用層 (Application Layer):協調領域物件完成業務流程
    • 基礎設施層 (Infrastructure Layer):技術實作細節
    • 介面層 (Interface Layer):與外部互動

實踐層:具體應用方法

核心建模元素:

實體 (Entity)
├─ 有唯一識別碼 (ID)
└─ 例:User、Order

值物件 (Value Object)
├─ 無識別碼,由屬性值定義
└─ 例:Address、Money

聚合 (Aggregate)
├─ 一組相關物件的集合
├─ 有聚合根 (Aggregate Root) 統一管理
└─ 例:訂單聚合 = Order + OrderItems

領域服務 (Domain Service)
├─ 不屬於單一實體的業務邏輯
└─ 例:轉帳服務需要兩個帳戶協作

領域事件 (Domain Event)
├─ 記錄業務中發生的重要事實
└─ 例:OrderPlaced、PaymentCompleted

實際案例:電商訂單系統

// 值物件:金額
class Money {
  constructor(
    private amount: number,
    private currency: string
  ) {}
  
  add(other: Money): Money {
    if (this.currency !== other.currency) {
      throw new Error('幣別不同無法相加');
    }
    return new Money(this.amount + other.amount, this.currency);
  }
}

// 實體:訂單項目
class OrderItem {
  constructor(
    public id: string,
    public productId: string,
    public quantity: number,
    public price: Money
  ) {}
}

// 聚合根:訂單
class Order {
  private items: OrderItem[] = [];
  
  constructor(public id: string) {}
  
  addItem(item: OrderItem): void {
    // 業務規則:同一商品不重複添加
    if (this.items.find(i => i.productId === item.productId)) {
      throw new Error('商品已存在');
    }
    this.items.push(item);
  }
  
  calculateTotal(): Money {
    return this.items.reduce(
      (total, item) => total.add(item.price),
      new Money(0, 'TWD')
    );
  }
}

關聯層:相關領域延伸

  • 微服務架構:DDD 的限界上下文是微服務劃分的理論基礎
  • 事件驅動架構 (EDA):領域事件可作為系統間的解耦機制
  • CQRS (命令查詢職責分離):常與 DDD 搭配,分離讀寫模型
  • 事件溯源 (Event Sourcing):透過領域事件重建系統狀態
  • 整潔架構 (Clean Architecture):與 DDD 分層理念相呼應

【深度問答】

Q1:當業務邏輯很簡單時,為什麼不能直接用 CRUD 而要採用 DDD?
→ DDD 適合「複雜且核心」的業務領域。如果只是簡單的增刪改查,使用 DDD 反而會過度設計,增加不必要的複雜度。判斷標準:業務規則是否頻繁變動?是否有複雜的業務約束?

Q2:為什麼不能讓多個聚合直接互相修改對方的資料?
→ 這會破壞聚合的「一致性邊界」。正確做法是透過領域事件或應用服務協調,確保每個聚合內部的資料一致性由聚合根負責。

Q3:通用語言只是改個變數名稱嗎?為什麼這麼重要?
→ 不只是命名!通用語言要貫穿需求討論、文件、程式碼、測試。當 PM 說「取消訂單」,工程師的 method 也叫 cancelOrder(),這能大幅降低認知負擔與溝通成本。


【可視化建議】

  1. 限界上下文地圖 (Context Map):用方塊圖展示不同子系統的邊界與關係
  2. 聚合結構圖:樹狀圖呈現聚合根與內部實體、值物件的關係
  3. 事件風暴 (Event Storming) 時間軸:橫向時間軸標示領域事件流程
  4. 分層架構圖:同心圓或分層矩形展示依賴方向(由外向內)

【進階路徑】

  1. 戰術設計深化
    關鍵字:Aggregate DesignRepository PatternFactory PatternSpecification Pattern
  2. 戰略設計探索
    關鍵字:Context MappingAnti-Corruption LayerShared KernelCustomer-Supplier
  3. 實戰整合架構
    關鍵字:CQRS + Event SourcingHexagonal ArchitectureEvent-Driven Microservices

【知識分類標籤】

#type/framework
#tech/backend/architecture
#depth/understanding
#usage/problem-solving

【心智圖】

id: ddd-mindmap
name: DDD 領域驅動設計心智圖
type: mermaid
content: |-
  flowchart LR
    DDD[Domain-Driven Design]
    DDD --> Strategy[戰略設計]
    DDD --> Tactical[戰術設計]
    DDD --> Philosophy[核心理念]
    
    Philosophy --> UL[通用語言]
    Philosophy --> BC[限界上下文]
    
    Strategy --> ContextMap[上下文映射]
    Strategy --> Subdomain[子領域劃分]
    Subdomain --> Core[核心域]
    Subdomain --> Supporting[支撐域]
    Subdomain --> Generic[通用域]
    
    Tactical --> Entity[實體]
    Tactical --> VO[值物件]
    Tactical --> Aggregate[聚合]
    Tactical --> Service[領域服務]
    Tactical --> Event[領域事件]
    Tactical --> Repository[倉儲]
    
    Aggregate --> AggRoot[聚合根]
    
    DDD --> Related[相關架構]
    Related --> Microservices[微服務]
    Related --> CQRS[CQRS]
    Related --> EventSourcing[事件溯源]
    Related --> CleanArch[整潔架構]

💡 給你的建議

根據你的特質(Ideation + Strategic + Learner),DDD 非常適合你:

  • 創意發揮:設計限界上下文與建模需要大量創造性思維
  • 策略思考:戰略設計層面需要全局觀與系統性規劃
  • 持續學習:DDD 是個深坑,有大量延伸知識可探索

行動建議

  1. 先從一個真實專案的「單一聚合」開始練習(例如訂單或購物車)
  2. 嘗試用「事件風暴」工作坊方式,與團隊一起梳理業務流程
  3. 閱讀 Eric Evans 的《Domain-Driven Design》藍皮書,或 Vaughn Vernon 的《實作領域驅動設計》紅皮書