<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Compatibility on Tarragon</title><link>https://tarrragon.github.io/blog/tags/compatibility/</link><description>Recent content in Compatibility on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/compatibility/index.xml" rel="self" type="application/rss+xml"/><item><title>11.6 向後相容的變更紀律</title><link>https://tarrragon.github.io/blog/backend/11-api-design/backward-compatibility-discipline/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/backend/11-api-design/backward-compatibility-discipline/</guid><description>&lt;p>向後相容的變更紀律回答一個高頻的日常問題：這個 diff 能不能直接上。版本策略（&lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5&lt;/a>）處理「決定要 breaking 之後怎麼辦」、本章處理更前面的一層 — 怎麼在每次變更時判定它 break 不 break、以及這個判定由人還是由工具把關。&lt;/p>
&lt;h2 id="breaking-的定義要明文且比直覺寬">Breaking 的定義要明文、且比直覺寬&lt;/h2>
&lt;p>變更紀律的地基是一份「什麼算 breaking」的明文清單、而且清單的範圍比直覺預期的寬。直覺抓得到的：刪欄位、改型別、改必填。直覺常漏的：改欄位預設值（消費者依賴舊預設）、改錯誤碼（消費者的分支邏輯建在上面）、改回應時序（輪詢邏輯依賴）、收緊驗證規則（昨天合法的請求今天 400）。反向的參照是 Stripe 明文的相容變更清單 — 新增資源、新增 optional 參數、新增 response property、property 順序改變、opaque ID 的長度格式改變、新增 event type（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/versioning-stripe-named-major-releases/" data-link-title="11.C11 Stripe 現行方案：具名 major release 與相容變更清單" data-link-desc="同一家公司版本策略隨規模演進的第二個時間切片、附「什麼算 backwards-compatible」的明文清單">11.C11&lt;/a>）：清單同時劃出「這些軸服務端保留自由」、消費者不可依賴。兩份清單（breaking 清單、相容清單）合起來才是完整的契約邊界、只有其中一份時灰色地帶照樣存在。&lt;/p>
&lt;h2 id="紀律的三個放置層格式工具流程">紀律的三個放置層：格式、工具、流程&lt;/h2>
&lt;p>相容紀律可以放在三個層、強度遞減、適用情境不同。&lt;/p>
&lt;p>&lt;strong>格式層&lt;/strong>：相容性做成編碼格式的性質、違規在技術上不可行或立即失效。protobuf 是代表 — field number 一旦投入使用即不可變更、刪除必須 reserve、重用會造成解碼歧義與資料損毀（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/grpc-protobuf-field-number-discipline/" data-link-title="11.C28 protobuf 官方規範：field number 紀律" data-link-desc="編號不可改、刪除必 reserve、重用導致資料損毀；契約相容性是編碼格式的性質、不是 review 慣例">11.C28&lt;/a>）；官方文件直接把 schema 變更分成 wire-safe、wire-unsafe 與 conditionally wire-compatible 三類 — 判定規則明文化之後、不依賴資深工程師在場。GraphQL 的 versionless 紀律同型、案例判讀把它歸納為三個支柱：只加不改、deprecation 標注、nullable 預設、由 schema 語言承載（C26 的判讀整理、觀察層見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/graphql-versionless-evolution/" data-link-title="11.C26 GraphQL 官方：versionless API 與 nullable-by-default" data-link-desc="no-versioning 的成本轉嫁鏈：只加不改、deprecation、nullable 預設三個紀律換掉版本號">11.C26&lt;/a>；GraphQL 內部機制的深化見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進&lt;/a>）。&lt;/p>
&lt;p>&lt;strong>工具層&lt;/strong>：相容檢查做成 CI gate、在 merge 前擋下。Buf 的 breaking detection 對比歷史 schema、在 merge 前擋下破壞性變更、規則分四級（FILE、PACKAGE、WIRE_JSON、WIRE）、文件明言「Catching this before merge is the point」（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/grpc-buf-breaking-detection/" data-link-title="11.C29 Buf breaking detection：四級規則對應消費者依賴" data-link-desc="把 proto 相容紀律從人的自律升級成 CI gate、檢查粒度是產品決策不是工具預設">11.C29&lt;/a>）。從四級的分級設計可以抽出選級判準（C29 判讀）：選符合消費者實際依賴的等級 — 只走 wire 的消費者用 WIRE、有 generated code 依賴的要更嚴的級。這條主張可以推廣成本章的通用判準：&lt;strong>相容性檢查的粒度是產品決策、不是工具預設&lt;/strong> — 檢查太嚴、內部重構寸步難行；太鬆、消費者實際依賴的層沒被保護。HTTP+JSON 的對應工具是 OpenAPI diff 類檢查、把 spec 當 schema 跑同樣的 gate（工具治理見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-governance/" data-link-title="11.10 API 規範治理" data-link-desc="設計規範怎麼讓幾十個團隊持續遵守 — 提案制、Guild 制、分軌制的治理模式比較、linting 進 CI、規範失敗的成因">11.10 規範治理&lt;/a>）。&lt;/p>
&lt;p>&lt;strong>流程層&lt;/strong>：格式與工具都蓋不到的語意變更（預設值、時序、驗證規則）、由變更審查流程把關 — review checklist 上有「對照 breaking 清單」一項、重大變更走 API design review。流程層是三層裡唯一蓋得住全部變更類型的、也是唯一依賴人自覺的 — 所以務實的配置是三層疊加：格式層鎖結構、工具層鎖 spec、流程層鎖語意。&lt;/p>
&lt;h2 id="到期與豁免的邊界設計">到期與豁免的邊界設計&lt;/h2>
&lt;p>紀律需要兩個邊界條款才能長期運作。&lt;strong>到期行為&lt;/strong>：宣告過的 breaking 變更到期執行時、回明確錯誤而非靜默改語意 — 原則的完整推導與 Facebook Graph v1.0 的反例展開見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/api-boundary-responsibility/" data-link-title="11.1 API 作為服務邊界的責任" data-link-desc="介面變更該由誰付成本、哪些介面性質算對外承諾、承諾違約有哪些模式 — 動手設計 endpoint 前的責任框架">11.1 的違約模式段&lt;/a>；審查視角的增量是把到期行為當成變更提案的必填欄位、跟 brownout 這類預告機制（見 &lt;a href="https://tarrragon.github.io/blog/backend/11-api-design/cases/versioning-github-password-auth-brownout/" data-link-title="11.C13 GitHub：密碼認證廢止的 brownout 執行" data-link-desc="deprecation 執行機制案例：公告觸及不到的長尾 client、用排程 brownout 的短暫真實故障叫醒">11.C13&lt;/a>）一起在 review 時就定案、而非退場當天即興。&lt;strong>豁免宣告&lt;/strong>：每次變更公告要明列「誰不受影響」— GitHub 的密碼認證廢止同時明列 2FA 使用者、GHES、GitHub App 不受影響、讓多數消費者第一段就能停止閱讀、注意力留給真正要動的人。&lt;/p></description><content:encoded><![CDATA[<p>向後相容的變更紀律回答一個高頻的日常問題：這個 diff 能不能直接上。版本策略（<a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5</a>）處理「決定要 breaking 之後怎麼辦」、本章處理更前面的一層 — 怎麼在每次變更時判定它 break 不 break、以及這個判定由人還是由工具把關。</p>
<h2 id="breaking-的定義要明文且比直覺寬">Breaking 的定義要明文、且比直覺寬</h2>
<p>變更紀律的地基是一份「什麼算 breaking」的明文清單、而且清單的範圍比直覺預期的寬。直覺抓得到的：刪欄位、改型別、改必填。直覺常漏的：改欄位預設值（消費者依賴舊預設）、改錯誤碼（消費者的分支邏輯建在上面）、改回應時序（輪詢邏輯依賴）、收緊驗證規則（昨天合法的請求今天 400）。反向的參照是 Stripe 明文的相容變更清單 — 新增資源、新增 optional 參數、新增 response property、property 順序改變、opaque ID 的長度格式改變、新增 event type（見 <a href="/blog/backend/11-api-design/cases/versioning-stripe-named-major-releases/" data-link-title="11.C11 Stripe 現行方案：具名 major release 與相容變更清單" data-link-desc="同一家公司版本策略隨規模演進的第二個時間切片、附「什麼算 backwards-compatible」的明文清單">11.C11</a>）：清單同時劃出「這些軸服務端保留自由」、消費者不可依賴。兩份清單（breaking 清單、相容清單）合起來才是完整的契約邊界、只有其中一份時灰色地帶照樣存在。</p>
<h2 id="紀律的三個放置層格式工具流程">紀律的三個放置層：格式、工具、流程</h2>
<p>相容紀律可以放在三個層、強度遞減、適用情境不同。</p>
<p><strong>格式層</strong>：相容性做成編碼格式的性質、違規在技術上不可行或立即失效。protobuf 是代表 — field number 一旦投入使用即不可變更、刪除必須 reserve、重用會造成解碼歧義與資料損毀（見 <a href="/blog/backend/11-api-design/cases/grpc-protobuf-field-number-discipline/" data-link-title="11.C28 protobuf 官方規範：field number 紀律" data-link-desc="編號不可改、刪除必 reserve、重用導致資料損毀；契約相容性是編碼格式的性質、不是 review 慣例">11.C28</a>）；官方文件直接把 schema 變更分成 wire-safe、wire-unsafe 與 conditionally wire-compatible 三類 — 判定規則明文化之後、不依賴資深工程師在場。GraphQL 的 versionless 紀律同型、案例判讀把它歸納為三個支柱：只加不改、deprecation 標注、nullable 預設、由 schema 語言承載（C26 的判讀整理、觀察層見 <a href="/blog/backend/11-api-design/cases/graphql-versionless-evolution/" data-link-title="11.C26 GraphQL 官方：versionless API 與 nullable-by-default" data-link-desc="no-versioning 的成本轉嫁鏈：只加不改、deprecation、nullable 預設三個紀律換掉版本號">11.C26</a>；GraphQL 內部機制的深化見 <a href="/blog/backend/11-api-design/styles/graphql/graphql-schema-evolution/" data-link-title="GraphQL Schema 演進：versionless 的紀律代價" data-link-desc="只加不改、deprecation 標注、nullable 預設怎麼共同取代版本號 — 以及每個紀律各自的隱藏帳單">Schema 演進</a>）。</p>
<p><strong>工具層</strong>：相容檢查做成 CI gate、在 merge 前擋下。Buf 的 breaking detection 對比歷史 schema、在 merge 前擋下破壞性變更、規則分四級（FILE、PACKAGE、WIRE_JSON、WIRE）、文件明言「Catching this before merge is the point」（見 <a href="/blog/backend/11-api-design/cases/grpc-buf-breaking-detection/" data-link-title="11.C29 Buf breaking detection：四級規則對應消費者依賴" data-link-desc="把 proto 相容紀律從人的自律升級成 CI gate、檢查粒度是產品決策不是工具預設">11.C29</a>）。從四級的分級設計可以抽出選級判準（C29 判讀）：選符合消費者實際依賴的等級 — 只走 wire 的消費者用 WIRE、有 generated code 依賴的要更嚴的級。這條主張可以推廣成本章的通用判準：<strong>相容性檢查的粒度是產品決策、不是工具預設</strong> — 檢查太嚴、內部重構寸步難行；太鬆、消費者實際依賴的層沒被保護。HTTP+JSON 的對應工具是 OpenAPI diff 類檢查、把 spec 當 schema 跑同樣的 gate（工具治理見 <a href="/blog/backend/11-api-design/api-governance/" data-link-title="11.10 API 規範治理" data-link-desc="設計規範怎麼讓幾十個團隊持續遵守 — 提案制、Guild 制、分軌制的治理模式比較、linting 進 CI、規範失敗的成因">11.10 規範治理</a>）。</p>
<p><strong>流程層</strong>：格式與工具都蓋不到的語意變更（預設值、時序、驗證規則）、由變更審查流程把關 — review checklist 上有「對照 breaking 清單」一項、重大變更走 API design review。流程層是三層裡唯一蓋得住全部變更類型的、也是唯一依賴人自覺的 — 所以務實的配置是三層疊加：格式層鎖結構、工具層鎖 spec、流程層鎖語意。</p>
<h2 id="到期與豁免的邊界設計">到期與豁免的邊界設計</h2>
<p>紀律需要兩個邊界條款才能長期運作。<strong>到期行為</strong>：宣告過的 breaking 變更到期執行時、回明確錯誤而非靜默改語意 — 原則的完整推導與 Facebook Graph v1.0 的反例展開見 <a href="/blog/backend/11-api-design/api-boundary-responsibility/" data-link-title="11.1 API 作為服務邊界的責任" data-link-desc="介面變更該由誰付成本、哪些介面性質算對外承諾、承諾違約有哪些模式 — 動手設計 endpoint 前的責任框架">11.1 的違約模式段</a>；審查視角的增量是把到期行為當成變更提案的必填欄位、跟 brownout 這類預告機制（見 <a href="/blog/backend/11-api-design/cases/versioning-github-password-auth-brownout/" data-link-title="11.C13 GitHub：密碼認證廢止的 brownout 執行" data-link-desc="deprecation 執行機制案例：公告觸及不到的長尾 client、用排程 brownout 的短暫真實故障叫醒">11.C13</a>）一起在 review 時就定案、而非退場當天即興。<strong>豁免宣告</strong>：每次變更公告要明列「誰不受影響」— GitHub 的密碼認證廢止同時明列 2FA 使用者、GHES、GitHub App 不受影響、讓多數消費者第一段就能停止閱讀、注意力留給真正要動的人。</p>
<h2 id="判讀訊號">判讀訊號</h2>
<table>
  <thead>
      <tr>
          <th>訊號</th>
          <th>判讀</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>「這算不算 breaking」在 review 裡反覆吵</td>
          <td>缺明文清單、先立清單、吵架轉為修清單</td>
      </tr>
      <tr>
          <td>相容性事故的變更當初過了 CI</td>
          <td>檢查粒度低於消費者實際依賴的層、對照 buf 四級思路重選</td>
      </tr>
      <tr>
          <td>內部重構常被相容檢查誤擋</td>
          <td>檢查粒度高於任何消費者依賴的層、同上反向調整</td>
      </tr>
      <tr>
          <td>消費者依賴了相容清單裡宣告可變的性質</td>
          <td>契約已明文、屬消費者責任、但值得檢討清單的傳達位置</td>
      </tr>
  </tbody>
</table>
<p>四個訊號的排查有方向性：前一個的修法是後三個的前提 — 清單沒立好、CI 粒度沒有校準對象；粒度爭議反覆出現、多半是清單跟消費者依賴的實況脫節。從清單開始修、工具與流程的爭論通常跟著消失。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>Breaking 決定要做之後的分期與退場：<a href="/blog/backend/11-api-design/versioning-and-deprecation/" data-link-title="11.5 版本策略與 deprecation" data-link-desc="版本方案怎麼選（URI/header/date-based）、支援窗口怎麼承諾、舊版怎麼安全退場 — 承諾分期與回收的操作設計">11.5 版本策略與 deprecation</a></li>
<li>消費端驗證（consumer-driven contract test 把「誰依賴什麼」顯性化）：<a href="/blog/backend/06-reliability/contract-testing/" data-link-title="6.10 Contract Testing 與 Schema 演進" data-link-desc="把跨服務 / API / event schema 的隱性期待變成可驗證契約，控制演進相容性">6.10 契約測試</a></li>
<li>相容檢查工具進 CI 的組織面：<a href="/blog/backend/11-api-design/api-governance/" data-link-title="11.10 API 規範治理" data-link-desc="設計規範怎麼讓幾十個團隊持續遵守 — 提案制、Guild 制、分軌制的治理模式比較、linting 進 CI、規範失敗的成因">11.10 API 規範治理</a></li>
<li>案例原文：<a href="/blog/backend/11-api-design/cases/" data-link-title="模組十一案例庫：API 設計與對外契約" data-link-desc="API 風格流派、版本與相容、介面語意、規範治理的已驗證公開案例集；含反例與覆蓋缺口標明">模組十一案例庫</a></li>
</ul>
]]></content:encoded></item></channel></rss>