🎯 什麼情境該想到我
當你「開發者和領域專家在同一件事上用不同名詞、需求討論和程式碼裡的詞兜不起來」的時候。若某個講法在對話中很順、寫進模型卻卡卡的,那通常代表模型本身有問題,而不是措辭問題。
⚙️ 怎麼用(步驟 / 公式)
意圖:讓團隊(開發者與領域專家)圍繞領域模型,建立並使用「同一套」語彙,貫穿日常對話、圖、文件與程式碼。語言不一致,就是模型不清的訊號。
做法要點:
- 以領域模型為骨幹,挑出核心概念與其關係,替每個概念定一個大家都同意的名字。
- 讓這套詞同時出現在口頭討論、需求、圖示、類別/方法命名裡——同一件事只用同一個詞。
- 說話卡住或詞不達意時,別急著繞過,把它當成模型有缺陷的線索,回頭修模型與語言。
- 語言會演化:當領域理解變深,同步更新語彙與程式碼中的命名,保持一致。
★★ 它不是「寫一份詞彙表」,而是「讓程式碼、對話、文件用同一組詞」
★ 失敗的樣貌(極常見):
會議上說「訂單被取消」
程式碼裡是 order.status = 2
資料庫欄位叫 ord_st
API 回傳 {"state": "CANCELLED"}
客服系統叫「作廢」
→ ★★ 每次溝通都要做一次翻譯,而★ 每次翻譯都是一次出錯機會
★★ 判準:你能不能直接把領域專家說的話,當成型別與方法的名字?
可以 → 通用語言成立。
需要「翻譯一下」→ 那個翻譯就是 bug 的溫床。
★★ Go 的落地:讓語言出現在型別系統裡
// ❌ 用技術詞彙包裝業務概念
type Order struct {
Status int // ★★ 2 是什麼?
Flag1 bool
}
func (o *Order) Update(m map[string]any) error // ★ "Update" 不是業務動作
// ✅ ★ 領域專家講得出來的每個名詞與動詞,都有對應的型別與方法
type Status int
const (
StatusDraft Status = iota
StatusPlaced
StatusCancelled // ★ 專家說「取消」→ 程式碼就叫 Cancelled
)
func (o *Order) Place() error { … } // ★ 「下單」
func (o *Order) Cancel(reason CancelReason) error { … } // ★ 「取消」,而且取消要有理由★★ 最有效的一個具體技巧:把專家講的「不可以」變成一個具名錯誤。
var (
ErrAlreadyShipped = errors.New("已出貨的訂單不可取消")
ErrInsufficientQty = errors.New("庫存不足")
)★ 這讓「業務規則」變成可以被 grep、被測試、被討論的東西——
而且客服看到的錯誤訊息和工程師看到的是同一句話。
★★ 三個最容易漏掉的邊界
① ★★ 資料庫欄位名
★ 領域裡叫 cancelled,DB 叫 ord_st=2 → ★ 每個 SQL 都要翻譯一次
→ ★ 新專案一律讓 DB 欄位名等於領域詞彙;★★ 舊系統用 [[防腐層]] 隔開
② ★★ API 的對外名稱
★ 它是「另一個上下文」的語言(見 [[發布語言]])—— ★ 不一定要等於內部詞彙
→ ★★ 但必須有一份明確的對映,而且那份對映住在 [[資料傳輸物件]] 裡
③ ★★ 錯誤訊息與日誌
★ 「validation failed on field 3」→ 客服、PM、工程師都看不懂
→ ★ 用領域語言寫錯誤:「已出貨的訂單不可取消(訂單 #42)」
★★ 怎麼「建立」它:三個實際可做的動作
① ★★ 事件風暴(Event Storming)—— ★ 最有效的一個
★ 把領域專家和工程師放在同一個房間,用便利貼寫「發生了什麼事」(過去式)
→ ★ 一兩小時就能產出:事件清單、名詞、以及★★ 意見不一致的地方
→ ★★ 那些「不一致的地方」就是 [[限界上下文]] 的邊界候選
② ★ 把詞彙表放在程式碼裡,不是 wiki
★ 一個 domain/glossary.go 或型別上的 doc comment
→ ★★ wiki 上的詞彙表三個月後一定過時;程式碼裡的會被重構工具帶著走
③ ★★ 在 code review 裡糾正用詞
★ 這是唯一能讓它持續的機制 —— 而且成本極低
→ ★ 「這裡叫 user,但我們的語言是 customer」是一個合理的 review 意見
★★ 它的邊界:通用語言只在「一個限界上下文內」通用
★★ 「Ubiquitous」不是「全公司統一」。
★ 在訂單上下文,「客戶」是「下單的人 + 收件資訊」
★ 在行銷上下文,「客戶」是「一組人口屬性與行為標籤」
→ ★★ 強迫兩者統一 = 一個誰都不滿意的上帝模型(見 [[標準資料模型]] 的同一陷阱)
→ ★ 正確做法:★★ 各自的上下文內部嚴格一致,跨上下文用翻譯([[防腐層]]/[[上下文對應]])
★ 這是 DDD 最常被誤讀的一句:「統一語言」聽起來像全公司統一,
而 Evans 的意思正好相反——它是有邊界的。
🧪 我實際套用的紀錄
- (待填)
⚠️ 注意 / 什麼時候不適用
-
通用語言只在一個限界上下文內保證一致;跨上下文同一個詞可能有不同意義,別強求全公司統一一套詞。
-
它要靠領域專家與開發者持續對話維護,不是寫一份詞彙表就一勞永逸。
-
若領域專家不參與、只有工程單方面命名,很容易退化成技術黑話,失去「共用」的意義。
-
★★ 「Ubiquitous」不是「全公司統一」,而是「一個限界上下文內統一」。 強迫跨上下文統一會得到一個誰都不滿意的上帝模型。
-
★★ 判準:能不能直接把領域專家說的話當成型別與方法的名字? 需要「翻譯一下」→ 那個翻譯就是 bug 溫床。
-
★ 詞彙表要放在程式碼裡(doc comment/glossary.go),不是 wiki。 wiki 版三個月後一定過時。
-
★★ 三個最容易漏掉的邊界:資料庫欄位名、API 對外名稱、錯誤訊息與日誌。
-
★ 把「業務上的不可以」變成具名錯誤(
ErrAlreadyShipped),讓規則可 grep、可測試、可討論。 -
★★ code review 糾正用詞是唯一能讓它持續的機制,而且成本極低。
-
★ 事件風暴一兩小時就能找出「大家意見不一致的地方」,而那些正是上下文邊界的候選。
-
不要為了通用語言而把技術詞彙全部改成業務詞彙:
repository、handler、mutex是技術層的詞,它們不在領域語言的範圍內。