向後相容的變更紀律回答一個高頻的日常問題:這個 diff 能不能直接上。版本策略(11.5)處理「決定要 breaking 之後怎麼辦」、本章處理更前面的一層 — 怎麼在每次變更時判定它 break 不 break、以及這個判定由人還是由工具把關。

Breaking 的定義要明文、且比直覺寬

變更紀律的地基是一份「什麼算 breaking」的明文清單、而且清單的範圍比直覺預期的寬。直覺抓得到的:刪欄位、改型別、改必填。直覺常漏的:改欄位預設值(消費者依賴舊預設)、改錯誤碼(消費者的分支邏輯建在上面)、改回應時序(輪詢邏輯依賴)、收緊驗證規則(昨天合法的請求今天 400)。反向的參照是 Stripe 明文的相容變更清單 — 新增資源、新增 optional 參數、新增 response property、property 順序改變、opaque ID 的長度格式改變、新增 event type(見 11.C11):清單同時劃出「這些軸服務端保留自由」、消費者不可依賴。兩份清單(breaking 清單、相容清單)合起來才是完整的契約邊界、只有其中一份時灰色地帶照樣存在。

紀律強度由消費者的更新能力決定

相容紀律要多嚴,取決於舊版消費者會存在多久——而這件事由消費者能不能被強制更新決定,不由自己的工程偏好決定。三種形態的下限差距很大。

消費者可以跟著一起部署:內部微服務之間、單一前端搭配自家後端。改介面時兩邊同時上線,breaking 的成本接近於零、紀律可以輕。這個形態的陷阱是它會悄悄失效——「一起上」在三個服務時成立、在三十個時不成立,而失效的那一天沒有任何訊號。

消費者自己決定何時更新:對外的 HTTP API、平台團隊對產品團隊。舊版會存在數月到數年、長度由對方的排期決定,所以紀律要撐得住「宣告之後還要等很久」這件事,到期與豁免的邊界條款在這個形態才開始有份量。

消費者無法被強制更新:行動應用(使用者不更新就是不更新)、發佈出去的 SDK、嵌進第三方系統的整合。舊版永遠在線,breaking 等於放棄那一部分使用者。這個形態讓「先上線再收斂」這條路不存在,紀律的下限因此由它決定。

同一個服務可以同時面對多種消費者,這時紀律取最嚴的那一種——有一個行動應用在用,整條介面就進入第三形態。

紀律的三個放置層:格式、工具、流程

相容紀律可以放在三個層、強度遞減、適用情境不同。前兩層能不能用,取決於介面有沒有機器可讀的 schema:protobuf 與 GraphQL 自帶,OpenAPI 是後補的一份,而手寫文件的 HTTP API 兩層都落空、只剩流程層。

格式層:相容性做成編碼格式的性質、違規在技術上不可行或立即失效。protobuf 是代表 — field number 一旦投入使用即不可變更、刪除必須 reserve、重用會造成解碼歧義與資料損毀(見 11.C28);官方文件直接把 schema 變更分成 wire-safe、wire-unsafe 與 conditionally wire-compatible 三類 — 判定規則明文化之後、不依賴資深工程師在場(gRPC 內部機制的深化見 proto 演進紀律)。GraphQL 的 versionless 紀律同型、案例判讀把它歸納為三個支柱:只加不改、deprecation 標注、nullable 預設、由 schema 語言承載(C26 的判讀整理、觀察層見 11.C26;GraphQL 內部機制的深化見 Schema 演進)。

工具層:相容檢查做成 CI gate、在 merge 前擋下。Buf 的 breaking detection 對比歷史 schema、在 merge 前擋下破壞性變更、規則分四級(FILE、PACKAGE、WIRE_JSON、WIRE)、文件明言「Catching this before merge is the point」(見 11.C29)。從四級的分級設計可以抽出選級判準(C29 判讀):選符合消費者實際依賴的等級 — 只走 wire 的消費者用 WIRE、有 generated code 依賴的要更嚴的級。這條主張可以推廣成本章的通用判準:相容性檢查的粒度是產品決策、不是工具預設 — 檢查太嚴、內部重構寸步難行;太鬆、消費者實際依賴的層沒被保護。HTTP+JSON 的對應工具是 OpenAPI diff 類檢查、把 spec 當 schema 跑同樣的 gate(工具治理見 11.10 規範治理)。

流程層:格式與工具都蓋不到的語意變更(預設值、時序、驗證規則)、由變更審查流程把關 — review checklist 上有「對照 breaking 清單」一項、重大變更走 API design review。流程層是三層裡唯一蓋得住全部變更類型的、也是唯一依賴人自覺的 — 所以務實的配置是三層疊加:格式層鎖結構、工具層鎖 spec、流程層鎖語意。

到期與豁免的邊界設計

紀律需要兩個邊界條款才能長期運作。到期行為:宣告過的 breaking 變更到期執行時、回明確錯誤而非靜默改語意 — 原則的完整推導與 Facebook Graph v1.0 的反例展開見 11.1 的違約模式段;審查視角的增量是把到期行為當成變更提案的必填欄位、跟 brownout 這類預告機制(見 11.C13)一起在 review 時就定案、而非退場當天即興。豁免宣告:每次變更公告要明列「誰不受影響」— GitHub 的密碼認證廢止同時明列 2FA 使用者、GHES、GitHub App 不受影響、讓多數消費者第一段就能停止閱讀、注意力留給真正要動的人。

判讀訊號

訊號判讀
「這算不算 breaking」在 review 裡反覆吵缺明文清單、先立清單、吵架轉為修清單
相容性事故的變更當初過了 CI檢查粒度低於消費者實際依賴的層、對照 buf 四級思路重選
內部重構常被相容檢查誤擋檢查粒度高於任何消費者依賴的層、同上反向調整
消費者依賴了相容清單裡宣告可變的性質契約已明文、屬消費者責任、但值得檢討清單的傳達位置

四個訊號的排查有方向性:前一個的修法是後三個的前提 — 清單沒立好、CI 粒度沒有校準對象;粒度爭議反覆出現、多半是清單跟消費者依賴的實況脫節。從清單開始修、工具與流程的爭論通常跟著消失。

下一步路由