本章探討如何在大型系統中聚焦核心問題,避免被大量次要議題淹沒。Distillation(蒸餾)是將混合物分離出精華的過程——在 Domain Model 中,就是區分出真正讓軟體與眾不同、值得投資建構的部分:Core Domain。
Strategic Distillation 能達成以下目標:
- 幫助團隊掌握系統整體設計及各部分如何協作
- 識別出可管理規模的核心模型,納入 Ubiquitous Language
- 引導重構方向
- 將工作聚焦於模型中最具價值的部分
- 指導外包決策、現成元件採用與人員分配

Figure 15.1: A navigation map for strategic distillation
Core Domain#
在大型系統中,有太多不可或缺的元件,真正的業務資產——Domain Model 的精華——容易被遮蔽和忽視。
問題本質#
- 難以理解的系統就難以變更,變更的影響也難以預見
- 開發者被迫專精於特定模組,知識傳遞因此受阻
- 當開發者侷限於各自模組,系統整合品質下降,工作分配彈性也降低
- 重複程式碼悄悄出現,系統變得更加複雜
最頂尖的開發者往往被吸引去做技術基礎設施或容易定義的 domain 問題,因為這些工作看起來有趣、能建立可轉移的專業技能。而真正區分應用程式、使其成為商業資產的 Core Domain,反而落入經驗較淺的開發者手中。
解決策略#
精煉模型,找出 Core Domain,並提供容易區分它與大量支援性模型和程式碼的手段。 讓最有價值、最專業的概念突顯出來,保持 Core 盡可能小。
關鍵行動:
- 將頂尖人才分配到 Core Domain,並據此招募
- 在 Core 中投入精力,尋找 Deep Model 並開發 Supple Design
- 讓其他部分的投資以「如何支援提煉後的 Core」為依據
- 需要保密作為競爭優勢的部分就是 Core Domain
- 當時間有限必須在兩個重構之間抉擇時,優先選擇最影響 Core Domain 的那個
Choosing the Core#
Core Domain 的選擇取決於觀點。例如,多數應用只需通用的貨幣模型,但貨幣交易應用可能需要更精細的貨幣模型作為 Core 的一部分。
- 一個應用的 Core Domain 可能是另一個應用的 Generic Supporting Component
- 在同一專案甚至同一公司內,通常能定義出一致的 Core
- Core Domain 的辨識應隨迭代演進——起初看似核心的物件可能只是支援角色
Who Does the Work?#
- 技術最強的成員通常對 domain 了解最少,這限制了他們的貢獻並強化了將其分配到支援元件的傾向
- 必須打破此惡性循環:組建由長期投入的強力開發者與深諳業務的 Domain Expert 組成的團隊
- 短期外部設計專家不適合直接建構 Core Domain(因為團隊需要累積 domain 知識,臨時成員會造成知識流失),但擔任教學/指導角色則非常有價值
- Core Domain 通常無法購買——即使有產業框架,客製軟體最大價值來自對 Core Domain 的完全掌控
如果一個框架過度約束了你的 Core Domain,可能有三種情況:(1) 你正在失去關鍵軟體資產;(2) 該框架涵蓋的範圍其實不如你想的那麼核心,應重新劃定 Core 邊界;(3) 你的 Core Domain 沒有特殊需求,考慮購買軟體整合即可。
An Escalation of Distillations#
蒸餾技術的激進程度由低到高:
- Domain Vision Statement — 以最少投資傳達基本概念與價值
- Highlighted Core — 改善溝通並引導決策,幾乎不需修改設計
- Generic Subdomains — 透過重構和重新封裝,明確分離通用子領域
- Cohesive Mechanisms — 將機制封裝為靈活、富表達力的設計
- Segregated Core — 在程式碼中直接呈現 Core,促進未來對 Core Model 的工作
- Abstract Core — 以最純粹的形式表達最基本的概念與關係(需要大幅重組與重構)
每一層蒸餾都需要更大的承諾,但刀刃磨得越細越鋒利。
flowchart BT
L1["1. Domain Vision Statement\n最少投資"] --> L2["2. Highlighted Core\n幾乎不改設計"]
L2 --> L3["3. Generic Subdomains\n重構分離"]
L3 --> L4["4. Cohesive Mechanisms\n封裝機制"]
L4 --> L5["5. Segregated Core\n程式碼層級分離"]
L5 --> L6["6. Abstract Core\n最純粹形式"]
InvestLabel["投入遞增 →"] ~~~ L1
L6 ~~~ EffectLabel["← 效果遞增"]
style InvestLabel fill:none,stroke:none,color:#666
style EffectLabel fill:none,stroke:none,color:#666Generic Subdomains#
模型中有些部分增加複雜度但不傳達專業知識——任何無關的東西都會讓 Core Domain 更難辨識。這些通用元素對系統運作和模型完整表達不可或缺,但它們抽象的概念在許多不同業務中都會用到(如組織架構圖、會計模型等)。
識別出不是專案動機的內聚子領域,將其通用模型抽離到獨立 Modules 中,不留下任何你的專業特色。 分離後,給予它們比 Core Domain 更低的開發優先順序。
四種實作選項#
Option 1: Off-the-Shelf Solution(現成方案)
| 優點 | 缺點 |
|---|---|
| 減少開發量 | 需要時間評估和理解 |
| 維護負擔外部化 | 品質無法保證 |
| 通常更成熟、更完備 | 可能過度工程化,整合成本高於簡單自製 |
| 可能引入平台或編譯器版本依賴 |
Option 2: Published Design or Model(已發表的設計或模型)
| 優點 | 缺點 |
|---|---|
| 比自製模型更成熟、反映眾多見解 | 可能不完全符合需求或過度工程化 |
| 自帶高品質文件 |
- 當領域已有高度形式化的嚴謹模型(如會計學、物理學),應直接採用
Option 3: Outsourced Implementation(外包實作)
| 優點 | 缺點 |
|---|---|
| 核心團隊可專注 Core Domain | 仍需核心團隊投入時間溝通介面和編碼標準 |
| 不需永久擴大團隊 | 接收程式碼有額外負擔 |
| 強制 interface-oriented 設計 |
- 自動化測試在外包中扮演重要角色——實作者應提供 unit test,最理想的做法是由團隊撰寫自動化驗收測試
Option 4: In-House Implementation(自行實作)
| 優點 | 缺點 |
|---|---|
| 容易整合 | 持續維護和培訓負擔 |
| 量身訂做 | 容易低估開發時間和成本 |
| 可指派臨時承包人 |
範例:A Tale of Two Time Zones#
兩個專案都花費頂尖開發者數週時間處理時區問題,形成鮮明對比:
- 貨運排程專案:Core Domain 已成熟,確認需要時區功能後才指派短期承包開發者進行研究與實作,最終成功交付
- 保險理賠專案:Core Domain 尚未開發,就先行指派長期開發者(本應累積 domain 知識的人)投入時區問題,需求不明確下嘗試全面通用化,結果程式碼從未被使用
兩個專案都正確地將 Generic 時區模型與 Core Domain 乾淨地隔離。但保險專案在 Core Model 尚未成熟時就分散注意力,而貨運專案則是在 Core 成熟後才分配資源處理 Generic 問題。
Generic 不代表 Reusable#
- 分離 Generic Subdomain 時,不應關注程式碼重用性——這違背了 distillation 的動機
- Model 層級的重用(如採用已發表的設計)往往比 code 層級的重用更有價值
- 必須嚴格保持在通用概念範圍內——引入產業特定元素會妨礙未來擴展,且那些專業概念應放在 Core Domain 或更專門的 subdomain 中
Project Risk Management#
- Agile 流程強調先處理高風險任務,但小心不要因為 Generic Subdomain 比較容易分析就先建構它
- 除非團隊技能已證明且 domain 非常熟悉,第一版系統應基於 Core Domain 的某個部分
- Core Domain 是高風險的:經常比預期困難,且沒有它專案不可能成功
Domain Vision Statement#
在專案初期模型尚未存在時,就需要聚焦開發方向。在後期,需要一種不需深入研究模型就能解釋系統價值的方式。此外,Core Domain Model 的關鍵面向可能跨越多個 Bounded Contexts。
撰寫一份簡短的描述(約一頁),闡述 Core Domain 及其帶來的價值(即「value proposition」)。忽略那些無法區分此 Domain Model 與其他模型的面向。展示 Domain Model 如何服務並平衡不同利益。保持精簡。儘早撰寫並隨著新洞察持續修訂。
Domain Vision Statement 的用途:
- 作為指引,讓開發團隊在持續蒸餾模型與程式碼的過程中保持共同方向
- 可與非技術團隊成員、管理層甚至客戶分享
Domain Vision Statement 應聚焦於 domain model 的本質及其對企業的價值,而非技術架構、UI 設計或效能需求等面向。例如,航空訂位系統的 Vision Statement 應描述旅客優先順序模型和航空公司訂位策略,而非 XML 介面設計或 logo 快取策略。
Highlighted Core#
Domain Vision Statement 以宏觀方式辨識 Core Domain,但將具體 Core 模型元素的辨識留給個人詮釋。除非團隊溝通品質極高,否則單靠 Vision Statement 影響有限。
不同的人不會選出完全相同的元素,甚至同一人在不同天的判斷也不一致。持續篩選模型以辨識關鍵部分會消耗本可用於設計思考的專注力。
The Distillation Document#
撰寫一份極簡短的文件(三到七頁稀疏內容),描述 Core Domain 及 Core 元素之間的主要互動。
常見風險及應對:
- 文件可能未被維護 — 保持絕對精簡,聚焦於中心抽象及其互動(這層模型通常更穩定)
- 文件可能未被閱讀 — 為非技術成員撰寫,作為所有人探索模型和程式碼的起點
- 多重資訊來源可能反而增加複雜度 — 遠離瑣碎細節
The Flagged Core#
在模型的主要儲存庫中標記 Core Domain 的元素,不特別闡述其角色,讓開發者能毫不費力地知道什麼在 Core 內、什麼在 Core 外。
具體手段可以是:
- UML 圖中使用 stereotype 標識 Core 元素
- 程式碼中使用註解(如 JavaDoc)
- 開發環境中使用某種工具
- 紙本文件上的頁籤和螢光筆標記
The Distillation Document as Process Tool#
Distillation Document 可作為流程工具:
- 當開發者發現其程式碼或模型變更需要同步修改 Distillation Document 時,就需要與團隊諮商
- 這代表他們正在根本性地改變 Core Domain 元素或關係,或改變 Core 的邊界
- Core 外的變更或未包含在文件中的細節變更,可在不諮商或通知的情況下整合
Cohesive Mechanisms#
封裝機制是物件導向設計的標準原則——將複雜演算法隱藏在具有 Intention-Revealing Name 的方法中,分離「what」與「how」。但當計算複雜度膨脹到一定程度,概念性的「what」會被機制性的「how」淹沒。
將概念上內聚的機制分離到獨立的輕量框架中。 特別注意已形式化或有良好文件記載的演算法類別。透過 Intention-Revealing Interface 暴露框架能力,讓 domain 的其他元素專注於表達問題(what),將解決方案的複雜性(how)委託給框架。
職責分離原則:
- Model(Core Domain 或 Generic Subdomain):表述事實、規則或問題
- Cohesive Mechanism:依模型的規範解決規則或完成計算
範例:組織架構圖中的 Mechanism#
一個需要精細組織架構模型的專案,大部分複雜度涉及遍歷組織樹的特定分支(搜尋特定人員或關係)。團隊認識到這正是 Graph(圖論)形式化能解決的問題。
- 一位承包商實作了 graph traversal 框架作為 Cohesive Mechanism
- 組織模型可簡單地用標準圖論術語宣告:每個人是 node,人與人之間的關係是 edge
- 如果機制被嵌入 domain model 中,模型會與特定解法耦合,且組織模型會變得複雜混亂
- 保持機制與模型分離允許以 declarative style 描述組織結構
Generic Subdomain vs. Cohesive Mechanism#
| Generic Subdomain | Cohesive Mechanism | |
|---|---|---|
| 本質 | 基於表達性模型,代表團隊對 domain 某方面的理解 | 不代表 domain,而是解決表達性模型提出的棘手計算問題 |
| 與 Core 的關係 | 與 Core Domain 性質相同,只是較不核心、較不重要 | 為模型服務的計算引擎 |
「A model proposes; a Cohesive Mechanism disposes.」(模型提出問題;機制處理問題。)在實務中,除非識別出已形式化的計算,這個區分通常不會一開始就很純粹。透過連續重構,它可能被蒸餾為更純粹的機制,或轉化為一個帶有先前未識別的模型概念的 Generic Subdomain。
When a Mechanism Is Part of the Core Domain#
幾乎總是應該將 Mechanism 從 Core Domain 中移除。唯一例外是機制本身是專有的且是軟體價值的關鍵部分——例如物流應用中特別有效的排程演算法,或投資銀行中高度專有的風險評估演算法。
即使在這些情況下,更深入的分析可能揭示演算法只是一組規則的特定實作,這些規則可以被明確表達並配上封裝的求解機制。
Distilling to a Declarative Style#
當 Cohesive Mechanisms 透過 Intention-Revealing Interface 提供存取,配合概念上一致的 Assertions 和 Side-Effect-Free Functions,Core Domain 就能做出有意義的陳述,而非呼叫晦澀的函式。當 Core Domain 的一部分突破到 Deep Model 並開始作為一種語言運作時,就能靈活、精煉地表達最重要的應用場景。
Segregated Core#
即使抽離了 Generic Subdomains,Core 元素仍可能與通用元素緊密耦合,概念內聚性不夠強或不夠明顯。所有這些混亂和糾纏會扼殺 Core。
重構模型,將 Core 概念從支援角色(包括定義不清的部分)中分離出來,強化 Core 的內聚性同時減少它與其他程式碼的耦合。將所有通用或支援性元素重構到其他物件和其他 packages 中,即使這意味著拆開高度耦合的元素。
重構步驟#
- 辨識一個 Core subdomain(可能從 Distillation Document 出發)
- 將相關類別移到以概念命名的新 Module
- 重構程式碼,切斷不直接表達該概念的資料和功能,放到其他 packages 的(可能是新的)類別中
- 重構新分離的 Core Module,使其關係和互動更簡單、更具溝通性,並最小化與其他 Modules 的關係
- 對其他 Core subdomain 重複以上步驟,直到 Segregated Core 完成
The Costs of Creating a Segregated Core#
- 分離 Core 有時會使與緊密耦合的非 Core 類別的關係更晦澀或更複雜——但這被釐清 Core Domain 的好處所抵消
- 為了凸顯 Core Domain 的內聚性,可能需要打破某個原本內聚良好的 Module——這是淨正收益
- 分離 Core 需要大量工作,可能需要開發者在整個系統中進行變更
- 最適合進行 Segregated Core 的時機:有一個對系統至關重要的大型 Bounded Context,但其核心部分被大量支援功能遮蔽
Evolving Team Decision#
整個團隊必須一起邁向 Segregated Core。此決策需要團隊級的流程和紀律:
- 約束每個人使用相同的 Core 定義,同時不凍結該定義
- 新洞察必須持續與團隊分享,但個人不能單方面行動
- 無論決策流程是共識制還是團隊領導指令制,都必須足夠敏捷以進行反覆的路線修正
範例:Segregating the CORE of a Cargo Shipping Model#

Figure 15.2: A model for cargo shipping coordination
以貨運協調軟體為例,從 Domain Vision Statement 出發:
"…Increase visibility of operations and provide tools to fulfill customer requirements faster and more reliably…"
此應用是為第一線操作人員設計的,因此所有與金錢相關的議題(pricing、invoices)都退居支援角色。焦點應放在貨物處理:依據客戶要求交付貨物。
分離後的關鍵洞察:
- Customer Agreement 現在直接約束 Handling Step(分離 Core 時典型的洞察浮現)
- Customer Agreement 直接附加到 Cargo 而非需要透過 Customer 導航——在實際交付時,Customer 不如 Agreement 本身與作業相關
- Customer 被拉出 Core——基本的 Customer 模型相當通用,有了直接可用的 Customer Agreement 後,交付互動通常不需要 Customer 類別

Figure 15.3: Reliable delivery in adherence with customer requirements is the core goal of this project.
完成 Segregated Core 後,可以接著對剩餘部分進行更有意義的封裝重構:

Figure 15.4: Meaningful MODULES for non-CORE subdomains follow after the SEGREGATED CORE is complete.
最終形成:一個 Segregated Core package、一個 Generic Subdomain、以及兩個扮演支援角色的 domain-specific packages。
Abstract Core#
即使是 Core Domain Model 通常也有太多細節,難以傳達全貌。我們通常透過將大模型拆解為更窄的 subdomains 並放入獨立 Modules 來管理。但這種分割有時會遮蔽甚至使 subdomains 之間的互動變得更複雜。
識別模型中最基本的概念,將其抽取為獨立的類別、抽象類別或介面。設計此抽象模型以表達重要元件之間的大部分互動。將此抽象整體模型放入獨立 Module,而專門化的詳細實作類別留在各自的 subdomain Modules 中。
關鍵要點:
- 這不是技術技巧——只有當 polymorphic interfaces 對應到 domain 中的基本概念時才有價值
- 抽取 Abstract Core 的過程不是機械性的,需要對關鍵概念及其在系統主要互動中的角色有深入理解
- Abstract Core 最終應類似 Distillation Document,但以程式碼撰寫,因此更嚴謹、更完整
考慮水平切割而非垂直切割。多型(Polymorphism)讓我們能忽略抽象型別實例間的大量細節差異。如果跨 Modules 的大部分互動都能在 polymorphic interfaces 層級表達,就值得將這些型別重構到一個特別的 Core Module 中。
Deep Models Distill#
Distillation 不僅在粗粒度層面將 domain 的部分從 Core 中分離,也意味著持續精煉那些 subdomains(特別是 Core Domain),透過不斷的 refactoring toward deeper insight,驅向 Deep Model 和 Supple Design。
- 目標是讓模型顯而易見的設計——能簡單地表達 domain
- Deep Model 將 domain 最本質的面向蒸餾為可組合解決重要問題的簡單元素
- 雖然 Deep Model 的突破在任何地方都有價值,但在 Core Domain 中它能改變整個專案的軌跡
Choosing Refactoring Targets#
面對分割不良的大型系統,Evans 不贊同兩種常見做法:
「到處開始,反正全都要重構」— 除非團隊全是頂尖程式設計師,否則不切實際「哪裡痛就重構哪裡」— 容易在邊緣修修補補、治標不治本,迴避最嚴重的糾結
正確的策略:
- 在 pain-driven refactoring 中,先看根源是否涉及 Core Domain 或 Core 與支援元素的關係——如果是,優先修復
- 在有餘裕自由重構時,優先改善 Core Domain 的分割、強化 Core 的隔離、並將支援性 subdomain 純化為 Generic
- 這是讓重構投資獲得最大回報的方式