🎯 什麼情境該想到我

當你「身為上游,有很多下游都要跟你整合,如果替每個下游各開一套特製介面會爆炸」的時候。你想提供一套公開、穩定的協定/服務,讓所有下游照同一個介面來接。

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

意圖:上游把自己的能力定義成一套公開、正式、穩定的協定或 API(一個開放的服務),供多個下游共用整合,而非為每個下游量身客製。

做法要點:

  1. 盤點下游共同需要的能力,設計成一套通用、對外一致的服務介面。
  2. 正式定義並文件化這套協定;盡量保持穩定與向後相容。
  3. 讓所有下游透過同一套介面整合;有特殊需求時再以擴充處理,而非另開分身。
  4. 常搭配發布語言作為交換的資料格式,讓協定的語彙也公開且良好定義。

★★ 它是上游的責任:為「多個你不認識的下游」設計一個穩定協定

★ 判準:★★ 你有幾個下游?
  ★ 一個 → [[客戶-供應商]] 就夠(★ 為那一個客製是合理的)
  ★★ 三個以上、或「你不知道有誰在用」→ ★ 開放主機服務
→ ★ 因為:★★ 為每個下游各自客製,★ 成本是 N 倍,而且它們會互相牽制

★★ 它和「有一個 API」的差別在於三件事:
★ ① 協定是為通用需求設計的,不是某個下游的需求;
★ ② 它有明確的版本與相容性承諾;
★ ③ 特殊需求走擴充點,不是改協定。

★★ Go/工程上的五個具體要求

① ★★ 版本策略要明確且寫下來
   ★ URL 版本(/v1/orders)vs header 版本 vs 只做向後相容
   → ★ 推薦:★★ 「只做向後相容的加法;不相容就開 /v2」
   → ★ 而「向後相容」要有定義:★★ 加可選欄位 ✅;改欄位語義 ❌;刪欄位 ❌

② ★★ 契約要是機器可讀的
   ★ OpenAPI 或 .proto,★★ 而且它是「真相」而非「文件」
   → ★ 由它產生 server stub 與 client → ★★ 文件不可能過時
   → ★ CI 檢查:★★ oapi-codegen/protoc 產生的碼和 repo 裡的一致

③ ★★ 錯誤格式要統一且穩定
   ★ RFC 9457 的 Problem Details(application/problem+json)是現成的選擇
   → ★★ 下游最痛的不是「功能不夠」,★ 而是「每個端點的錯誤格式都不一樣」

④ ★★ 要有「誰在用什麼」的可觀測性
   ★ 按 client id + 端點 + 欄位的 usage metric
   → ★★ 沒有這個,★ 你永遠不敢刪任何東西(見 [[客戶-供應商]] 的契約測試)

⑤ ★ 擴充點而非分支
   ★ 下游需要一個特殊欄位 → ★★ 不要在協定裡加 if clientType == "X"
   → ★ 用 ★★ 「選擇性欄位展開」(?expand=lines,customer)或
     ★ 一個明確的擴充區(metadata jsonb)

★★ ③ 最被低估:★ 下游整合一個 API 時,
一半的工作量花在「搞清楚各種失敗長什麼樣」——★ 統一錯誤格式直接砍掉那一半。

★★ 它的核心張力:通用性 vs 你自己的模型

★★ 開放主機服務逼你把「對外協定」和「內部模型」分開,★ 而這是必要的:
  ★ 內部模型要能自由重構(★ 它是你的核心)
  ★★ 對外協定必須穩定(★ 它是別人的依賴)
→ ★ 所以一定要有一層 [[資料傳輸物件]] + [[對應器]]
→ ★★ 「直接把領域型別序列化出去」在開放主機服務裡是明確的錯誤
   —— ★ 因為那讓「重構領域模型」變成「破壞所有下游」

★ 一個具體的檢查:★★ 你的領域型別上有 json tag 嗎?
有 → ★ 那你的內部模型已經是公開契約了(見 資料傳輸物件)。

★★ 什麼時候不要做開放主機服務

① ★★ 只有一個下游,而且它是你自己的前端
   → ★ 那是 BFF(見 [[遠端外觀]]),★★ 而 BFF 應該為那個前端客製
   → ★ 硬做成「通用協定」會讓前端拿到一堆不需要的欄位、要打三次請求

② ★ 下游的需求差異極大
   → ★★ 一個「通用」協定會退化成所有需求的聯集
     (★ 見 [[標準資料模型]] 的同一陷阱)
   → ★ 那時「每個下游一個 BFF」比一個開放主機服務更好

③ ★ 你還在探索邊界
   → ★★ 太早承諾穩定協定 ⇒ ★ 你會被自己的早期錯誤綁住
   → ★ 先 [[客戶-供應商]],等模式穩定了再升級

★★ ① 是最常見的誤用:★ 為了「架構正確」而把一個只服務自家前端的 API
做成通用協定——★★ 結果是前端體驗變差,而通用性沒有任何人受益。

★ 兄弟辨析

差別
發布語言★ 強調「格式」;開放主機服務強調「服務與協定」(★ 常一起出現)
客戶-供應商★ 為特定下游客製
遠端外觀★ BFF:為特定客戶端設計
標準資料模型★ 過度通用化的陷阱
防腐層★ 下游端的對應措施(★ 有好的開放主機服務時可以省掉)

🧪 我實際套用的紀錄

  • (待填)

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

  • 設計一套「對所有人都好」的通用協定比針對單一下游難;下游只有一個時未必值得。

  • 對外協定一旦公開就得維持穩定,改動受限、演化變慢,需要版本策略。

  • 若你就是想暴露自家內部模型,容易讓下游被你的模型綁死;介面應為對外設計而非內部翻版。

  • ★★ 判準:你有幾個下游? 一個且是自家前端 → 那是 BFF,應該客製。硬做成通用協定會讓前端體驗變差而沒有人受益。

  • ★★ 「直接把領域型別序列化出去」是明確的錯誤 —— 那讓「重構領域模型」變成「破壞所有下游」。檢查:領域型別上有 json tag 嗎?

  • ★★ 統一的錯誤格式是最被低估的一項。 下游整合的一半工作量花在「搞清楚各種失敗長什麼樣」。用 RFC 9457 Problem Details。

  • ★★ 契約要是機器可讀且是「真相」(由 OpenAPI/.proto 產生 stub),並在 CI 檢查產生的碼與 repo 一致——這樣文件不可能過時。

  • ★★ 要有「誰在用什麼」的 usage metric,否則你永遠不敢刪任何東西。

  • ★ 特殊需求走擴充點,不要在協定裡加 if clientType == "X"。

  • ★ 「向後相容」要有定義:加可選欄位 ✅、改欄位語義 ❌、刪欄位 ❌。

  • ★ 下游需求差異極大時,「每個下游一個 BFF」比一個通用協定更好(見 標準資料模型 的陷阱)。

  • 還在探索邊界時不要承諾穩定協定,你會被早期錯誤綁住。

🔗 相關工具