🎯 什麼情境該想到我

當你「兩個上下文是明確的上游供給、下游消費關係,而下游的需求該被上游正式納入規劃」的時候。你想把這種依賴變成有約定、可協商的關係,而不是下游單方面被動承受。

⚙️ 怎麼用(步驟 / 公式)

意圖:確立上游(供應商)與下游(客戶)的關係,讓下游的需求被上游納入規劃與優先序,並以自動化測試把介面約定固定下來,讓上游能安心變更。

做法要點:

  1. 明確指定誰是上游供應商、誰是下游客戶,以及流動方向。
  2. 建立協商機制:下游把需求提入上游的規劃流程,雙方一起排優先序與時程。
  3. 以下游驅動的驗收測試把介面約定固化;上游改動要先通過這些測試。
  4. 把測試納入上游的持續整合,讓上游能在不驚動下游的前提下自由演進內部。

★★ 它的核心是一個「義務」:上游必須為下游的需求排程

★ 客戶-供應商(Customer/Supplier)的定義:
  ★★ 上游(供應商)有義務考慮下游(客戶)的需求
  → ★ 三個具體的義務:
    ① ★★ 排程協商 —— ★ 下游的需求會進上游的 backlog
    ② ★★ 回歸測試 —— ★ 上游改動前要跑下游提供的測試
    ③ ★ 變更通知 —— ★ 破壞性變更要提前告知

★★ 判準:下游能不能把一張 ticket 放進上游的 backlog,而且它會被排?
可以 → ★ 這是客戶-供應商。
不行 → ★★ 那是 追隨者(★ 你只能照抄它給你的東西)。
★ 這個判準是組織性的,不是技術性的——★★ 而這正是 Evans 的重點。

★★ 讓它真正運作的關鍵機制:消費者驅動契約測試

★★ 「回歸測試」這個義務,如果沒有機制就是空話。
→ ★ 具體做法:★★ 下游提供一組測試,上游的 CI 必須跑它們
// ★ 下游(客戶)維護一組「我依賴你的什麼」的測試
// ★★ 它們放在上游的 repo 裡,或用 Pact 之類的 broker 共享
func TestOrderingAPIContract(t *testing.T) {
    // ★ 我依賴:GET /orders/{id} 回傳的 JSON 有 id、status、total_cents 三個欄位
    // ★★ 我不依賴:其他欄位(★ 所以上游可以自由增刪它們)
    resp := get(t, "/orders/ord_1")
    assertHasFields(t, resp, "id", "status", "total_cents")
}

★★ 這組測試最重要的性質是「它只斷言下游真正用到的東西」:

★ 斷言整個 JSON 相等 → ★★ 上游加一個欄位就紅燈 ⇒ 上游會開始討厭這組測試
★ 只斷言用到的欄位 → ★ 上游可以自由演進 ⇒ ★★ 契約測試才活得下去

★ 這是契約測試成敗的關鍵細節,而它常被寫錯。

★★ 三個實務上的摩擦點,以及各自的解法

① ★★ 上游不知道「誰在用什麼」
   ★ 於是不敢刪任何欄位(★ API 只增不減,十年後有 80 個欄位)
   → ✅ ★ 契約測試就是答案:★★ 沒有測試覆蓋的欄位就可以刪
   → ★ 或最低成本版:★ 對每個欄位加 usage metric(★ 三個月沒人讀就刪)

② ★★ 下游的需求排不進上游的 backlog
   ★ 上游有自己的 OKR,而下游的需求「對他們沒有價值」
   → ✅ ★ 這需要組織介入(★ 共同的目標、或把下游需求算進上游的 KPI)
   → ★★ 技術解決不了這個問題 —— ★ 而假裝能解決會讓關係退化成 [[追隨者]]

③ ★ 破壞性變更的通知期
   → ★★ 要有一個明確的數字(★ 「破壞性變更提前兩個 sprint 通知」)
   → ★ 沒有數字的「會提前通知」等於沒有承諾

★★ ② 是最根本的:★ 客戶-供應商關係的存續依賴組織的支持,
而不是技術機制。→ ★★ 沒有組織支持時,誠實地把它畫成 追隨者 或 防腐層。

★★ 上游該提供什麼:三個層次

★ ① 最低:★ 一份穩定的 API + 版本策略
★ ② 中等:★★ + 消費者驅動契約測試 + 破壞性變更的通知期
★★ ③ 完整:★ 以上 + [[開放主機服務]](★ 一個為多個下游設計的穩定協定)
           + [[發布語言]](★ 有版本的交換格式)

★ 判準:下游有幾個?
一個 → ★ ② 就夠。
多個 → ★★ 升級成 開放主機服務,否則你會為每個下游各自客製。

★★ 下游該做什麼(這一頁常被寫成只有上游的義務)

① ★★ 明確表達「我依賴什麼」(★ 就是契約測試)
   → ★ 沒有這個,上游只能猜,而猜的結果是「什麼都不敢改」

② ★★ 不要依賴未公開的行為
   ★ 「這個欄位雖然沒寫在文件裡,但它總是有值」→ ★★ 那不是契約
   → ★ 依賴它,你就失去了抱怨的立場

③ ★ 自己做一層薄的隔離
   → ★★ 即使是客戶-供應商,★ 在邊界上做一次轉換仍然值得
     (★ 一個 [[對應器]],不必是完整的 [[防腐層]])
   → ★ 這讓「上游改了一個欄位名」的影響範圍是一個檔案,不是三十個

★★ ③ 是投資報酬率最高的一項:★ 成本是一個對應函式,
收益是「上游的變更不會擴散到你的領域層」。

★ 兄弟辨析

差別
合作關係★ 對等、共同存亡
追隨者★★ 上游沒有配合義務
防腐層★ 下游單方面付出翻譯成本
開放主機服務★ 上游為多個下游設計的穩定協定

🧪 我實際套用的紀錄

  • (待填)

⚠️ 注意 / 什麼時候不適用

  • 前提是上游團隊願意且有動機服務下游;若上游根本不理下游,這模式撐不起來(此時下游多半只能追隨者或建防腐層)。

  • 一個上游有多個下游時,需求會互相衝突,優先序協調成本上升。

  • 過度綁定下游的驗收測試也可能拖慢上游,要平衡「約定」與「上游自主」。

  • ★★ 判準:下游能不能把 ticket 放進上游的 backlog,而且它會被排? 不行 → 那是 追隨者,不是客戶-供應商。

  • ★★ 契約測試只能斷言「下游真正用到的欄位」。 斷言整個 JSON 相等 → 上游加欄位就紅燈 → 上游會討厭這組測試 → 契約測試死掉。

  • ★★ 「下游需求排不進上游 backlog」是組織問題,技術解決不了。 假裝能解決會讓關係退化成 追隨者。

  • ★★ 破壞性變更的通知期要有明確數字。 沒有數字的「會提前通知」等於沒有承諾。

  • ★★ 契約測試同時解決「上游不敢刪欄位」的問題:沒有測試覆蓋的欄位就可以刪。

  • ★ 下游也有義務:明確表達依賴什麼、不依賴未公開的行為、在邊界做一層薄的轉換。

  • ★ 在邊界做一次轉換(一個 對應器)是投資報酬率最高的一項——上游改欄位名的影響範圍是一個檔案。

  • ★ 下游超過一個 → 升級成 開放主機服務,否則會為每個下游各自客製。

🔗 相關工具