🌏 Read this article in English
我看過太多團隊把 DDD vs TDD 當宗教戰爭打。
直到一次 review 被前輩問:「你到底想解什麼?」我才發現,我講的根本不是同一件事。
那次之後我學會一件事:這三個東西根本不在同一張牌桌上。一個處理「語意對不上」,一個處理「介面吵不停」,一個處理「改一行壞三處」。把它們擺在一起比優劣,就像問螺絲起子跟扳手哪個比較好用。
真正該問的是,這個 sprint 我到底卡在哪一格。
錯置的戰場:為什麼單一方法論無法通吃
D、S、T 對應的是三個不同維度:領域建模、介面定義、實現品質。不是三選一。
DDD:處理領域複雜度的利器
DDD 想解的問題很具體:讓業務專家跟工程師講同一種話。
refundOrder(退款)跟 cancelTransaction(取消交易)在業務上是兩回事,但在程式碼裡常常被混用。DDD 的「通用語言」就是逼著開發者跟業務坐下來,把這些術語對齊、映射到實體、值物件與聚合根上。
CRUD 系統用不到這套,邊際效益遞減。但業務規則複雜到連業務專家自己都講不清楚的時候——這時 D 的價值才真的長出來。前提是團隊有可持續的訪談與模型驗證節奏,沒這節奏,D 就只是一堆名詞作業。
SDD:確保系統整合的契約
這裡的 SDD 特指 Spec-driven Development(規格驅動開發),強調在寫程式碼前,先定義好模組間、服務間或 API 間的介面契約。
我記得前後端團隊在 Slack 上因為欄位定義不一致吵了一整個下午。當介面契約成為唯一的真理來源,這種溝通成本就消失了。S 解的不是「業務邏輯怎麼寫」,是「資料長什麼樣、怎麼傳遞」。
在多服務情境下,若缺少明確的介面契約,整合成本會隨服務數量與相依關係快速上升。OpenAPI、Protobuf 這些工具不是趕時髦,是讓團隊在分散式系統裡還能對得上話。
TDD:降低重構風險的保險費
TDD 不是測試。是設計。是讓你晚上敢按下 deploy 的那個保險。
先寫失敗的測試,逼自己想清楚「這功能要解決什麼」,產出的程式碼自然低耦合、高內聚。重構階段是 TDD 的靈魂,它讓你在滿足需求之後,還有空間把程式碼弄乾淨。
TDD 不是護城河,是保險費。你付時間,買的是未來重構時的自由。需求穩定的專案裡,這筆保費的邊際效益就會被其他品質投資追過。
TDD 是一種開發習慣,不是架構模式。它可以套在任何架構上(包括 D 或 S),但它本身不決定系統長什麼樣。
反直覺洞察:TDD 鎖的不是 bug,是你當下對領域的理解
TDD 在不同階段扮演不同角色,不能一概而論。
模型還在飄的階段,把測試綁死在實作細節上,等於把「現在的理解」鎖進系統。等理解變了,這層鎖就會變成調整成本。這時候 TDD 不是保險費,是綁手綁腳。
模型站穩之後,狀況反過來:這時 TDD 提供的是「大膽重構」的安全感。每次變更都有測試接住,你才敢動。
我自己的規則:模型還在飄的階段,只寫 contract test(鎖住對外行為),不寫綁到實作的 unit test。等模型站穩了再補。
要不要 TDD、什麼時候上 TDD,取決於團隊對「錯誤成本」與「重構頻率」的判斷,而不是看專案屬於哪個類型。
取捨討論:何時該用什麼?
選擇 D、S、T,不是三選一,是看你現在卡在哪。
情境 A:複雜業務邏輯的企業級應用
特徵:業務規則複雜、領域模型龐大、團隊規模大、需要與業務專家頻繁溝通。
選擇:D 為主,T 為輔。
理由:領域模型的準確性直接影響業務價值。聚合根與限界上下文能幫團隊劃分責任邊界。複雜的業務邏輯也需要 T 來確保每次變更不會破壞既有規則。
S 在這裡的位置:若主要複雜度都在單一領域模型內部,S 的優先級可降低。但只要涉及跨團隊 API、外部整合或合規資料交換,S 還是要納入。
情境 B:分散式微服務架構
特徵:服務數量多、團隊獨立部署、網路延遲敏感、需要一致的資料契約。
選擇:S 為主,T 為輔。
理由:微服務裡,服務間的通訊就是系統本身。S 把介面一致性與版本控制鎖住,避免後續整合成本爆掉。T 則確保每個服務內部邏輯不出錯。
D 在這裡的位置:微服務邊界還沒穩定時,D 的限界上下文可以協助切分;邊界已清楚就採輕量建模。把每個服務都搞成完整 DDD,常常是過度工程。
情境 C:快速迭代的 MVP 或內部工具
特徵:需求變動快、團隊規模小、業務邏輯簡單、強調開發速度。
選擇:輕量契約 + 針對高風險核心邏輯測試。
理由:MVP 階段全套 D 會拖慢速度。簡化 S(如基本的 RESTful API 規範)就夠團隊協作。T 只針對核心邏輯,防止技術債在你還沒找到 PMF 之前就先把你壓死。
我見過 MVP 團隊硬上 DDD,三週後 backlog 還在討論限界上下文叫什麼名字。那不是嚴謹,那是還沒搞清楚自己在做什麼。
D 在這裡:邊際效益遞減。簡單的 CRUD 邏輯,硬塞領域模型只會拖慢節奏。
Key Insight:D 解語意、S 解協作、T 解風險。三條軸線各自獨立評估,方法論的邊際效益才會顯現。
決策框架:三軸獨立評估
這三個維度各自獨立。不要套單一路徑,要三條軸線各自打分。
軸 1:領域複雜度 → 決定 D 的投入
- 業務專家自己都講不清楚規則 → 完整 D(聚合根、領域事件)
- 規則清楚但實體間關係複雜 → 簡化 D(實體與值物件)
- 簡單 CRUD → 不投 D
軸 2:協作邊界 → 決定 S 的投入
- 多團隊、多服務、版本管控嚴 → 嚴格 S(OpenAPI/Protobuf + 契約測試)
- 單一團隊跨服務 → 標準 S(OpenAPI 即可)
- 單體應用、小團隊 → 輕量 REST 規範
軸 3:錯誤與重構成本 → 決定 T 的投入
- 錯誤代價高、重構頻繁 → 完整 T(單元 + 整合 + 領域事件模擬)
- 中等變更頻率 → 單元測試 + 整合測試
- 低變更、低錯誤代價 → 手動測試 + 核心邏輯 T
三軸不會綁在一起變動。有可能你的領域複雜度低、但協作邊界很複雜(典型的多團隊內部工具),那就是 D 低、S 高、T 中。有可能領域複雜度高、但只有自己一個團隊在做(典型的核心引擎),那就是 D 高、S 低、T 高。
矩陣是給人參考的,不是給人套的。
關鍵洞察:T 是跨情境的基礎設施
當重構成本或錯誤成本高時,T 值得被當成基礎品質投資。
複雜度高的情境裡,T 的邊際效益最高,因為它降低了重構的風險。複雜度低的情境,T 的邊際效益較低,但依然能提供基本品質保障。差別在於「值不值得投到完整覆蓋」,不是「要不要寫測試」。
市場訊號與未來趨勢
目前市場上對 D、S、T 的討論,常常聚焦在「哪個更先進」。但從工程實踐角度看,這三個方法論正在融合,不是互斥。
我自己的賭法是:AI 會把 T 從紀律變成預設,但領域建模那一段它幫不上忙。
隨著微服務與 API 經濟的成熟,S 的普及度正在提高。OpenAPI、GraphQL、Protobuf 成為標準工具,介面契約成為團隊協作的基礎。
D 正在從「戰術模式的全面實施」轉向「核心概念的挑選」。許多團隊不再追求完整 D 實現,而是挑核心概念(如聚合根與領域事件)來處理複雜邏輯。
市場訊號尚未明朗:AI 輔助編程對 T 的長期影響,目前還在觀察期。等 2-3 年回頭看工具鏈成熟度與團隊實證資料,訊號會更清楚。
下一步:如何開始?
架構沒有標準答案,只有當下的權衡。
下個 sprint 的 backlog 攤開來看:你卡的是語意對不上、介面吵不停,還是改一行就壞三處?把預算押在那一格就好。剩下兩個,等它們真的痛了再說。
你今天的 sprint,卡在哪一格?
Sources
- Martin Fowler: Domain-Driven Design — DDD 經典定義與核心概念
- Eric Evans: Domain-Driven Design Reference — 通用語言與限界上下文原始定義
- OpenAPI Specification — SDD 介面契約標準
- Protobuf Documentation — SDD 序列化與合約定義
- Kent Beck: Test-Driven Development by Example — TDD 原始論述與紅綠重構循環
- ThoughtWorks: TDD: How it Benefits Software Development — TDD 實踐與邊際效益分析
- Microservices.io: Pattern – Microservice Architecture — 微服務情境下契約與邊界的工程實證