← 回文章列表

2024年4月30日

怎麼劃分 Domain?找出子域、分辨核心/支撐/通用,再對應到限界上下文

DDD領域驅動設計架構設計系統設計微服務

DDD 概論 裡簡單介紹了子域和限界上下文,Domain Storytelling 那篇也從故事裡找出了「搜尋、帶看、斡旋」三個候選邊界。但真的要替整個系統劃分時,還有幾個問題要回答:

  1. 子域要怎麼找? 只靠一個故事不夠,整個業務要從哪些角度看?
  2. 怎麼判斷是核心、支撐還是通用? 每個人都覺得自己負責的是核心
  3. 子域和限界上下文是一對一嗎? 系統要照子域切嗎?

先分清楚三個名詞

名詞屬於說明房仲的例子
領域(Domain)問題空間公司從事的整個業務房屋仲介
子域(Subdomain)問題空間業務中相對獨立的一塊能力物件刊登、物件媒合、帶看、斡旋成交、佣金業績
限界上下文(Bounded Context)解法空間一個模型與它的語言有效的範圍,是系統的切法刊登上下文、搜尋上下文、成交上下文

子域是「發現」的,限界上下文是「設計」的。 子域在寫任何程式之前就存在:就算公司全部用紙本作業,「帶看」這件事還是存在;限界上下文則是我們決定怎麼用軟體去解決它。

第一步:找出子域

沒有一個公式能直接算出子域,但有幾個角度可以交叉比對:

1. 從業務能力出發

問「公司做哪些事?」,用「動詞+名詞」列出業務能力,而不是列出系統功能或資料表:

  • 開發委託:說服屋主簽下委託
  • 刊登物件:把物件資訊、照片放上網站
  • 媒合買方:讓買方找到適合的物件
  • 安排帶看:協調買方、屋主、經紀人的時間
  • 斡旋議價:在買賣雙方之間協商價格
  • 簽約交屋:簽訂買賣契約、交屋
  • 計算佣金與業績:服務費怎麼拆給經紀人與分店

2. 從流程中的關鍵事件切

跑過 Event Storming 或畫過粗粒度的領域故事後,時間軸上會有幾個關鍵事件(Pivotal Event),它們是業務階段的分界:

委託已簽訂 → 物件已上架 → 帶看已完成 → 斡旋已接受 → 買賣契約已簽訂 → 已交屋

兩個關鍵事件之間的那一段,通常就是一個子域的候選。

3. 從語言的變化

同一個詞意思開始不一樣的地方,就是邊界:

  • 刊登時的「物件」是一則廣告,在意的是照片好不好看、文案吸不吸引人
  • 成交時的「物件」是一個交易標的,在意的是權狀、地號、有沒有抵押

4. 從組織與變化頻率

  • 誰負責? 刊登歸行銷、斡旋歸店長、佣金歸財務,不同部門的需求很少會同時改到
  • 多常改? 搜尋與推薦幾乎每個月都在調整,佣金規則一年才改一次

這四個角度得出的結果通常不會完全一致,不一致的地方就是要拿去和領域專家討論的地方。

第二步:分類

找出子域後,下一步是決定每一塊值得投資多少。分類的關鍵問題只有一個:這一塊做得比對手好,客戶會因此選擇我們嗎?

類型判斷問題策略
核心子域(Core)做得好會讓客戶選我們、做不好會失去客戶自己做,放最好的人,用完整的 DDD 設計,持續投資
支撐子域(Supporting)業務需要、買不到現成的,但做得再好客戶也不會在意自己做但做簡單,可以外包,不需要複雜的設計
通用子域(Generic)每家公司都有一樣的問題,市場上有成熟方案買現成的、用開源或雲端服務,不要自己寫

用 Core Domain Chart 畫出來

只用文字分類容易流於主觀,可以用 Nick Tune 提出的 Core Domain Chart,把每個子域放在兩個維度上:

  • 橫軸:模型複雜度,業務規則有多複雜、多難做對
  • 縱軸:業務差異化,對競爭優勢的影響有多大

房仲業務的 Core Domain Chart:橫軸是模型複雜度,縱軸是業務差異化。上半部差異化高的區域是核心子域,物件媒合與推薦位在右上角,複雜度和差異化都最高,斡旋成交流程也在核心區;下半部差異化低,左下是支撐子域,包含物件刊登、帶看排程、佣金與業績;右下是通用子域,複雜但不帶來差異化,包含會員與登入、地圖與地理資訊、金流,這些應該買現成方案。右側說明各區的投資策略

圖的讀法:

  • 右上(核心):複雜又能帶來差異,是最值得投資的地方。物件媒合做得好,買方更快找到房子,經紀人的帶看成交率也更高
  • 左下(支撐):簡單、不帶來差異,自己做但用最簡單的方式做。帶看排程只要能約時間、會提醒就夠了
  • 右下(通用):很複雜,但做得好也不會讓客戶選我們。金流、身分驗證自己寫很容易出錯,應該買現成的
  • 左上:簡單但有差異化,可能是一個剛出現的機會,或是很快會被對手模仿的功能

佣金與業績是個有趣的例子:規則其實不簡單(專任/一般委託的比例不同、跨店合作要拆帳),但它不影響客戶的選擇,所以是支撐子域。複雜不等於核心,這是分類時最常見的誤判。

分類不是永久的

  • 公司策略決定核心:同樣是房仲,主打「經紀人服務品質」的公司,帶看體驗可能就是核心;主打「線上找房」的公司,核心是搜尋和推薦
  • 核心會變成通用:十年前地圖找房是差異化,現在每個房屋網站都有,加上地圖服務越來越成熟,它就變成通用子域了

所以分類的結果要寫下來,並且定期(例如每半年)重新檢視一次。

第三步:對應到限界上下文

理想上,一個子域對應一個限界上下文,邊界最清楚。但實際上常見幾種情況:

  • 一個子域、多個上下文:物件媒合包含「搜尋」和「推薦」,兩者的模型不同(搜尋要的是篩選條件,推薦要的是瀏覽紀錄和相似度),可以切成兩個上下文
  • 多個子域、一個上下文:舊系統常常一個大系統包辦刊登、帶看、成交。短期內不一定要拆,但要知道它涵蓋了哪些子域,新功能盡量不要再往裡面塞
  • 通用子域變成外部服務:金流直接用第三方,在自己這邊加一層防腐層,把對方的格式轉成自己的模型

子域與限界上下文的對應:上方是問題空間的子域,物件刊登與帶看排程是支撐子域,物件媒合與斡旋成交是核心子域,金流是通用子域;下方是解法空間的限界上下文,物件刊登對應刊登上下文,物件媒合拆成搜尋與推薦兩個上下文,帶看排程對應帶看上下文,斡旋成交對應成交上下文,金流對應外部金流服務。每個上下文裡的「物件」模型都不同:刊登上下文是標題、照片、文案,搜尋上下文是座標、價格、坪數,推薦上下文是瀏覽紀錄與相似物件,帶看上下文只存物件 Id 與時段,成交上下文是權狀、地號與所有權人。刊登上下文透過「物件已上架」事件以公開語言通知搜尋與推薦,成交上下文透過防腐層串接外部金流服務

注意帶看上下文的「物件」只有 Id:帶看只需要知道是哪一間、地址在哪,不需要照片或權狀。每個上下文只保留自己需要的部分,這正是限界上下文的意義。

上下文之間的關係(上下文映射)也要一起決定:

  • 刊登 → 搜尋、推薦:刊登上下文發布「物件已上架」、「物件已改價」事件,用統一格式給多個下游(開放主機服務/公開語言),搜尋端再依事件更新自己的索引,做法類似 CQRS 的讀取模型
  • 成交 → 外部金流:用防腐層把金流服務的欄位與狀態碼轉成成交上下文的模型,換金流商時只需要改這一層

邊界切錯的徵兆

切完之後,可以用這些問題檢查:

  • 一個需求要同時改好幾個上下文? 偶爾發生是正常的,經常發生代表相關的規則被切開了
  • 上下文之間一直同步呼叫? 完成一件事要 A 呼叫 B、B 再呼叫 C,任何一個掛掉整個流程就失敗,代表它們其實是同一件事
  • 一個上下文需要兩個團隊一起改? 限界上下文最好由一個團隊負責,否則每次修改都要協調
  • 同一個上下文裡,同一個詞有兩種意思? 代表裡面其實有兩個模型,應該再切開

幾種常見的錯誤切法:

切法問題
依資料表切:物件服務、會員服務、訂單服務每個業務流程都要跨好幾個服務,「物件服務」又變回所有人共用的大模型
依技術層切:前端團隊、API 團隊、資料庫團隊任何功能都要三個團隊排程,沒有人對完整的業務負責
切得太細:每個聚合一個服務服務之間的呼叫和資料同步成本超過拆分的好處
照組織圖切,但組織本身就不合理康威定律會把組織的問題複製到系統裡,有時候要調整的是團隊分工

不同類型,不同做法

分類的目的是把力氣花在對的地方,不同類型的子域,架構和開發方式可以完全不同:

核心子域支撐子域通用子域
設計方式完整的戰術設計:聚合、值物件、領域事件簡單的分層或 Transaction Script,CRUD 也可以不自己設計,整合現成方案
架構六角形架構,隔離業務與技術能動、好維護就好用防腐層包起來
測試業務規則要有完整的單元測試主要流程的整合測試測試整合的那一層
人力最資深、最懂業務的人新人、外包都可以少量人力負責整合

「全部都用完整的 DDD」和「全部都不用」一樣不好。用在核心子域,才能得到 DDD 最大的回報。

一句話總結

子域是從業務能力、關鍵事件、語言變化與組織分工中「發現」出來的,用「做得好會不會讓客戶選我們」與模型複雜度把它們分成核心、支撐、通用;限界上下文則是我們「設計」的系統切法,理想上和子域一對一,但可以視模型差異再拆或暫時合併,每個上下文只保留自己需要的模型,最後把最多的心力放在核心子域。