以限界上下文、聚合體和通用語言,圍繞業務領域建模軟體。當使用者提及「領域建模」、「限界上下文」、「聚合根」、「通用語言」、「防腐層」、「上下文映射」、「領域事件」、「戰略設計」、「程式碼與業務不符」或「如何拆分這個大型系統」時使用。也適用於將單體拆分為服務、定義服務邊界或將程式碼結構與業務流程對齊時。涵蓋實體與值物件、領域事件及上下文映射策略。關於架構層次,請參見 clean-architecture。關於複雜度,請參見 software-design-philosophy。
領域驅動設計框架
透過圍繞業務領域建模程式碼來處理軟體複雜度的框架。軟體最大的風險並非技術失敗,而是建立一個無法反映業務實際運作方式的模型。
核心原則
模型即程式碼;程式碼即模型。 軟體應體現對業務領域深入且共享的理解。當領域專家與開發者使用相同的語言,且該語言直接表現在程式碼庫中時,複雜度變得可控,系統也能隨著業務變化優雅地演進。
評分
目標:10/10。 為領域模型評分時,快速診斷表每滿足一行得 1 分(共 7 行),再加上最多 3 分的深度分:核心領域有真正豐富的模型(不只是 CRUD)得 +1,不變條件存在於聚合體內而非服務中得 +1,通用語言在對話、程式碼和測試中一致得 +1。分級:9-10 = 專家可讀的名稱、明確的上下文邊界與防腐層、小型聚合體、行為豐富的實體、用於跨聚合體流程的事件、已識別的核心領域;5-6 = 有些領域語言但邊界洩漏或貧血物件;<=3 = 技術命名、單一模型涵蓋一切、邏輯散落在服務中。報告分數及未通過的特定診斷行。
框架
1. 通用語言
核心概念: 開發者與領域專家之間共享且嚴謹的語言,在對話、文件和程式碼中一致使用。當語言改變時,程式碼也跟著改變——而程式碼中彆扭的命名反過來促使語言精煉。
為何有效: 模糊性是大多數建模失敗的根本原因。當開發者說「訂單」而專家指的是「採購請求」時,錯誤無可避免;通用語言強制程式碼中的每個名稱都對應到業務認可且驗證的概念。
關鍵見解:
- 語言來自深度協作,而非事後附加的詞彙表
- 如果一個概念難以命名,模型很可能有問題——命名困難是設計訊號
- 技術術語(
DataProcessorvs.ClaimAdjudicator)將領域邏輯隱藏起來,讓專家無法修正 - 不同的限界上下文可能對同一個詞有不同的含義——這沒問題
程式碼應用:
| 上下文 | 模式 | 範例 |
|---|---|---|
| 類別/方法命名 | 以領域概念和動詞命名 | LoanApplication、policy.underwrite()——而非 RequestHandler、process() |
| 模組結構 | 按領域概念組織 | shipping/、billing/——而非 controllers/、services/ |
| 程式碼審查 | 拒絕純技術名稱 | 將 Manager、Helper、Processor、Utils 標記為命名壞味道 |
參見:references/ubiquitous-language.md 在進行建模會議或維護詞彙表時使用——涵蓋語言如何演進並回饋到程式碼。
2. 限界上下文與上下文映射
核心概念: 限界上下文是一個明確的邊界,在其中某個特定領域模型適用。同一個詞(「客戶」)在不同上下文中可能含義不同;上下文映射定義了它們之間的關係和轉換策略。
為何有效: 試圖維護單一統一模型的大型系統最終必然陷入不一致。限界上下文接受業務不同部分需要不同模型的事實;上下文映射管理它們之間的整合。
關鍵見解:
- 限界上下文不是微服務——它是語言和模型的邊界,可能包含多個服務
- 上下文邊界通常與團隊邊界一致(康威定律)
- 九種上下文映射模式描述了團隊之間的技術與政治關係
- 防腐層是最重要的防禦模式——絕不讓外來模型洩漏到你的核心領域
- 共享核心會耦合兩個團隊;保持小型且明確治理
- 從映射現狀開始(大泥球),然後定義目標邊界
程式碼應用:
| 上下文 | 模式 | 範例 |
|---|---|---|
| 服務整合 | 防腐層 | 在邊界處將外部 API 回應轉換為你的領域物件 |
| 舊系統遷移 | 順應者 / 防腐層 | 將舊系統包裝在一個適配器後面,使其使用你的領域語言 |
| API 設計 | 開放主機服務 + 發布語言 | 公開一個有良好文件且使用標準化綱要的 REST API |
參見:references/bounded-contexts.md 了解九種映射模式與整合策略。
3. 實體、值物件與聚合體
核心概念: 實體具有跨狀態變更持續存在的身份。值物件完全由其屬性定義且不可變。聚合體是實體與值物件的集群,由單一根確保一致性邊界。
為何有效: 沒有這些區分,所有東西都會變成可變且帶有身份的物件——狀態混亂、更新不一致、並發脆弱。聚合體劃清了界線:內部的一切保證一致;外部的一切最終一致。
關鍵見解:
- 實體測試:「即使我的所有屬性都改變了,我還是同一個東西嗎?」(一個人改變姓名和地址——仍然是同一個人)
- 值物件測試:「我只由我的屬性定義嗎?」(任何一張 10 美元鈔票都可互換)
- 大多數東西應該是值物件而非實體——偏好不可變性
- 保持聚合體小型(一個根加上最小集群);透過 ID 而非物件參考來參考其他聚合體
- 僅在聚合體內保持立即一致性;聚合體之間設計為最終一致性
程式碼應用:
| 上下文 | 模式 | 範例 |
|---|---|---|
| 身份追蹤 | 帶 ID 的實體 | Order 由 orderId 識別,在狀態變更後仍存在 |
| 不可變屬性 | 值物件 | Address(street, city, zip)——取代,永不修改 |
| 一致性邊界 | 聚合根 | Order 是根;OrderLine 項目只能透過它存在 |
| 並發控制 | 根上的樂觀鎖定 | Order 上的版本欄位;若兩個編輯競爭則衝突 |
參見:references/building-blocks.md 了解聚合體設計規則與一致性邊界。
4. 領域事件
核心概念: 領域事件捕捉領域中發生且專家關心的事情,以過去式命名(OrderPlaced、PaymentReceived)——一個已經發生的事實。
為何有效: 領域事件將原因與結果解耦。當 OrderPlaced 被發布時,出貨、帳務和通知各自獨立反應,而訂單上下文無需知道它們——減少耦合、最終一致性、自然的審計軌跡。
關鍵見解:
- 事件是不可變的事實——一旦發布,就不能更改或撤回
- 領域事件是限界上下文內部的;整合事件跨越邊界
- 事件實現時間解耦:生產者不等待消費者
- 事件溯源將完整的事件歷史儲存為真相來源,透過重播推導當前狀態
- 並非每個狀態變更都值得一個事件——只發布領域關心的事件
程式碼應用:
| 上下文 | 模式 | 範例 |
|---|---|---|
| 狀態轉換 | 在領域動作時引發事件 | order.place() 引發 OrderPlaced |
| 跨上下文整合 | 發布整合事件 | OrderPlaced 觸發出貨上下文中的 ShippingLabelRequested |
| 最終一致性 | 非同步事件處理器 | 庫存處理器在 OrderPlaced 後非同步更新庫存 |
參見:references/domain-events.md 了解事件命名、事件溯源與整合事件。
5. 儲存庫與工廠
核心概念: 儲存庫提供領域物件的記憶體集合幻覺,隱藏持久化。工廠封裝複雜的建立邏輯,確保聚合體總是在有效狀態下誕生。
為何有效: 當持久化和組裝細節洩漏到領域程式碼中時,每次儲存變更都會波及業務規則,且聚合體可能以半有效狀態被建構。儲存庫將 SQL/ORM 關注點限制在基礎設施中,使領域保持可在記憶體中測試;工廠使聚合體的唯一建構路徑能強制執行其不變條件,因此無效實例無法表示。
關鍵見解:
- 儲存庫介面屬於領域層;其實作屬於基礎設施
- 儲存庫方法使用通用語言:
findPendingOrders(),而非getByStatusCode(3) - 集合導向的儲存庫模仿
add/remove;持久化導向的則使用save - 工廠適用於複雜規則或多部分組裝;兩個欄位的值物件只需一個建構子
- 規格模式將查詢條件封裝為領域物件:
OverdueInvoiceSpecification
程式碼應用:
| 上下文 | 模式 | 範例 |
|---|---|---|
| 資料存取抽象 | 儲存庫介面 | 領域中的 OrderRepository.findByCustomer(customerId);基礎設施中的 PostgresOrderRepository |
| 複雜建立 | 工廠方法 | Order.createFromQuote(quote) 從 Quote 聚合體驗證並組裝 |
| 查詢封裝 | 規格 | spec = OverdueBy(days=30); repo.findMatching(spec) |
參見:references/repositories-factories.md 了解儲存庫、工廠與規格模式。
6. 戰略設計與提煉
核心概念: 系統中並非所有部分都同等重要。戰略設計識別核心領域——競爭優勢所在——並將其與支援性子領域(必要但非差異化)和通用子領域(商品化)區分開來。
為何有效: 對所有地方施加同樣的嚴謹度會分散最佳人才,並過度工程化商品功能。識別核心領域能將最好的開發者和最深入的建模集中在最重要的地方。
關鍵見解:
- 核心領域:投入最佳人才和最深度的建模;支援性:建構但不過度工程化;通用(認證、電子郵件、支付):購買或使用開源
- 提煉從周圍複雜度中提取並凸顯核心領域
- 領域願景陳述是核心領域價值主張的一頁描述
- 隨著業務演進重新審視何謂「核心」——今天的差異化可能成為明天的商品
程式碼應用:
| 上下文 | 模式 | 範例 |
|---|---|---|
| 自建 vs. 購買 | 分類子領域類型 | 自建定價引擎(核心);使用 Stripe 處理支付(通用) |
| 團隊分配 | 最佳開發者在核心領域 | 資深人員建模核保規則;初級人員整合電子郵件服務 |
| 程式碼組織 | 分離核心與通用 | domain/pricing/(深度模型)vs. infrastructure/email/(薄適配器) |
參見:references/strategic-design.md 在決定工程投入方向時使用——子領域分類與提煉技術。
常見錯誤
| 錯誤 | 為何失敗 | 修正 |
|---|---|---|
| 技術名稱而非領域語言 | 邏輯隱藏在 DataManager 後面;專家無法驗證模型 |
重新命名為領域術語(ClaimAdjudicator);若無領域術語,概念可能有誤 |
| 單一模型統治一切 | 單一 Customer 類別用於帳務、出貨和行銷,變得臃腫且矛盾 |
限界上下文:每個上下文有自己的 Customer,僅包含所需屬性 |
| 巨型聚合體 | 並發衝突、載入緩慢、交易瓶頸 | 保持聚合體小型;透過 ID 參考;聚合體之間最終一致性 |
| 貧血領域模型 | 物件是資料袋;規則散落在服務中且重複 | 將行為移入實體和值物件;服務僅負責編排 |
| 沒有防腐層 | 外來模型洩漏;程式碼耦合到外部綱要 | 在每個外部系統後方包裝一個轉換層 |
| 限界上下文 = 微服務 | 過早拆分;分散式複雜度卻無好處 | 上下文是模型邊界,而非部署單元;從單體中的模組開始 |
| 跳過領域專家 | 開發者發明不符合現實的模型;昂貴的重工 | 定期進行建模會議,直到專家說「對,就是這樣運作」 |
快速診斷
| 問題 | 若否 | 行動 |
|---|---|---|
| 領域專家能讀懂你的類別名稱嗎? | 技術術語隱藏了模型 | 將類別、方法、事件重新命名為通用語言 |
| 限界上下文邊界是否明確定義? | 模型互相滲透;同一詞彙含義不同 | 繪製上下文映射;定義邊界與轉換 |
| 聚合體是否小型(一個根 + 最小集群)? | 載入緩慢、並發問題 | 拆分聚合體;透過 ID 參考;接受最終一致性 |
| 領域物件是否包含行為而不只是資料? | 貧血模型;邏輯散落在服務中 | 將業務規則移入實體和值物件 |
| 是否使用領域事件進行跨聚合體通訊? | 緊耦合、同步鏈 | 引入事件;讓聚合體非同步反應 |
| 每個外部整合是否有防腐層? | 外來模型污染你的領域 | 在每個邊界加入轉換層 |
| 你是否已識別哪個子領域是核心? | 最佳人才分散 | 分類子領域;將深度建模集中在核心領域 |
延伸閱讀
關於完整方法論、模式與更深度的見解:
- 《領域驅動設計:軟體核心複雜度的解決之道》 by Eric Evans
關於作者
Eric Evans 是軟體設計顧問,也是領域驅動設計的創始人,其理念源自於金融、保險和物流領域大型系統的實務經驗。他於 2003 年出版的《領域驅動設計:軟體核心複雜度的解決之道》是史上最具影響力的軟體架構書籍之一,並持續透過其顧問公司 Domain Language 發展 DDD。






