題幹與適用場景
一個公開訂單 REST API 已經被 600 個第三方整合與多個無法強制升級的行動應用程式使用。現有 GET /v1/orders 會一次回傳全部訂單,每筆訂單包含字串欄位 customer_name,status 目前只會回傳 pending 或 paid。下一版希望把客戶資訊改成結構化物件、對清單分頁,並新增 refunded 狀態。
600 個整合、現有欄位與狀態都是本題的面試假設。核心限制是服務提供者無法控制所有使用者何時升級, 因此成功標準不只包括新用戶端能呼叫 v2,也包括舊用戶端繼續依照原契約使用 v1、遷移過程可觀測、 棄用日期可被發現,以及發布失敗時能恢復服務而不改變已承諾的版本語意。
這道題歸入 backend,因為核心能力是 API 契約、伺服器端表示層、發布與相容治理。它不要求設計整個 訂單系統,也不討論資料庫分片。面試中應先把目前契約寫清楚,再判斷每項變化;只說「URL 加 v2」 無法證明舊用戶端安全。
{
"orders": [
{
"id": "ord_1",
"customer_name": "Ada Lovelace",
"status": "paid"
}
]
}面試官考察點
第一個訊號是能否區分三類相容性。原始碼相容性關注舊用戶端重新產生或升級 SDK 後還能否編譯; 傳輸格式相容性關注舊序列化器能否解析新訊息;語意相容性關注相同呼叫是否仍取得合理使用者 所依賴的行為。欄位型別沒變不代表語意沒變,例如把「回傳全部訂單」悄悄改成「只回傳前 100 筆」, JSON 仍然合法,但舊用戶端會漏資料。
第二個訊號是能否依契約逐項判斷。絕對規則表無法涵蓋真實用戶端的解析方式。新增選用請求欄位且 預設行為不變,通常可以留在 v1;刪除、重新命名或改變欄位型別會破壞 v1。回應增加欄位只有在契約允許未知欄位、實際 SDK 也能忽略未知欄位時才是安全的增量。替回應列舉增加值尤其需要檢查:開放列舉契約可以允許擴充, 但封閉列舉、產生的強型別 SDK 或沒有 default 分支的窮舉判斷可能失敗。
第三個訊號是把版本、實作和生命週期連成一條可執行路徑。強回答會保留 v1 表示層,以統一領域邏輯 分別產生 v1 與 v2 回應;發布前執行契約差異檢查與舊 SDK 測試;發布後依使用者觀察採用率、錯誤率與 延遲;最後透過標準化棄用訊號、遷移指南和明確停止條件完成下線。版本號是路由選擇,不會自動完成 這些工作。
回答前需要澄清的問題
- 使用者是否可控? 若只有同一公司的三個服務且能以單次協調完成發布,可以採用擴充—遷移—收縮,
未必需要長期維護 v2。第三方與舊行動用戶端無法強制更新時,需要穩定版本邊界和公開生命週期。
- 目前契約是否要求忽略未知欄位和未知列舉? 這決定新增回應欄位或列舉值能否留在 v1。不能只
根據 JSON 本身可解析就宣布相容,還要查看 OpenAPI、SDK 型別和真實使用者行為。
- 清單目前規模與可靠性風險是什麼? 若一次回傳全部訂單仍滿足 SLO,可以只在 v2 提供分頁。
若無界回應已經威脅服務可用性,需要先限流並溝通緊急邊界,但仍不能把靜默截斷包裝成相容變更。
- 版本選擇機制是否已經公開? 既有 API 使用路徑版本就繼續
/v1與/v2;既有 API 使用日期
請求標頭就沿用請求標頭。遷移期間更換機制會額外製造一個用戶端變更。
- 能否辨識使用者並聯絡負責人? 有穩定應用程式 ID、SDK 版本和聯絡人時,可以精確追蹤遷移;
只能看匿名流量時,下線門檻必須更保守。
- 法律、合約或業務上承諾了多長支援期? 停止日期應由已發布政策、客戶義務、風險和真實採用率
決定,不能把其他平台的支援年限直接當成本系統標準。
30 秒回答框架
「我會先凍結 v1 的書面契約和可觀測行為,再把變化依原始碼、傳輸格式和語意相容性分類。新增選用 欄位且預設行為不變,可以考慮留在 v1;把字串換成物件、重新命名欄位和把完整清單改成分頁都需要 v2;新增回應列舉值要看開放列舉約定與舊 SDK。實作上共用訂單領域邏輯,只保留 v1、v2 兩套表示 轉接器。發布前跑 OpenAPI 差異、舊 SDK、錄製請求重播和端對端契約測試,v2 先讓明確使用者選擇加入。 接著依使用者監控版本採用率與錯誤,提供遷移指南、棄用與停止日期;只有遷移門檻和義務都滿足才關閉 v1。任何階段出錯都復原 v2 路由或轉接器,v1 的行為保持不變。」
分步驟深入解答
先建立相容基線。保存目前 OpenAPI 文件、已發布 SDK、代表性請求與回應、錯誤碼、排序、預設值和 分頁行為。文件沒有寫出的可見行為也要抽樣核對,因為使用者可能依賴欄位格式、空值、排序或一次 回傳全部結果。基線同時記錄使用者 ID、版本、請求量和負責人,後續才能判斷「沒人使用」還是「無法 辨識使用者」。
接著逐項分類:
| 計畫變更 | v1 判斷 | 處理方式 | |---|---|---| | 增加選用請求欄位,省略時維持舊行為 | 通常相容 | 在 v1 增加,並測試舊請求 | | 增加選用回應欄位 | 有條件相容 | 先驗證未知欄位策略與舊 SDK | | 把 customer_name 改成 customer 物件 | 不相容 | v1 保留字串,v2 回傳物件 | | 刪除或重新命名現有欄位 | 不相容 | 新版本新增名稱,舊版本不移除 | | 把完整清單預設改成分頁 | 語意不相容 | 在 v2 定義游標和頁面契約 | | 回應增加 refunded 列舉值 | 取決於列舉契約 | 檢查開放列舉、產生程式碼和窮舉分支 | | 修正文件未承諾且不會改變合理依賴的拼字 | 仍需證據 | 用使用者測試和流量重播證明 |
這裡最容易漏掉的是分頁。Google 的相容指引特別提醒:若舊介面預設回傳全部項目,後來新增一個 預設有限的 page_size,舊用戶端可能誤以為已經取得完整集合。正確做法是在 v2 明確 items、 nextpagetoken、排序與權杖失效規則;v1 在支援期內繼續履行舊行為,同時用配額、回應大小監控和 遷移溝通控制風險。
接著確定版本邊界。本題已經採用路徑版本,因此增加 /v2/orders,不在同一路徑上根據 User-Agent 猜版本,也不把 v1 的預設實作悄悄指向新語意。版本只涵蓋外部表示層:請求進入後先解析成統一的領域 命令,訂單查詢與權限邏輯共用,回傳時再由 V1OrderPresenter 或 V2OrderPresenter 產生對應形狀。 這樣錯誤修正和安全規則仍能同時涵蓋兩個版本,不需要複製整個服務。
v2 的範例契約可以是:
{
"orders": [
{
"id": "ord_1",
"customer": {
"display_name": "Ada Lovelace"
},
"status": "paid"
}
],
"next_page_token": "eyJvcmRlcl9pZCI6Im9yZF8xIn0"
}發布門禁分四層。第一層比較新舊 OpenAPI,阻止 v1 中的欄位刪除、必填性變化、型別變化與意外的新 驗證規則。第二層用最後一個公開 v1 SDK 編譯並執行固定契約案例,涵蓋未知欄位、空值、錯誤回應與 列舉。第三層重播去識別化後的代表性請求,比較 v1 新舊版本的狀態碼、關鍵欄位和排序。第四層讓 v2 在沙盒或 Canary 環境由少量已知使用者選擇加入,觀察功能、4xx、5xx、延遲和回應大小。Schema diff 只能發現結構變化,不能取代排序、預設值和分頁完整性的語意斷言。
遷移從「選擇使用」開始。發布 v2 文件、SDK、遷移對照表和雙版本沙盒;為每個已知使用者展示 v1 呼叫量、失敗端點和目標日期。先遷移內部範例與官方 SDK,才能及早發現指南缺口。每週依使用者查看 採用率,並另外核對總請求量:一個低流量的月底對帳整合可能比大量健康檢查更重要。核心指標包括 v1、 v2 的唯一活躍使用者數、請求量、4xx、5xx、p95 延遲、分頁完成率、舊 SDK 解析失敗,以及已聯絡但 未遷移的高風險帳戶。
棄用要區分三個時間點:公告遷移政策、API 正式進入棄用狀態、API 停止回應。RFC 9745 定義的 Deprecation 回應標頭傳遞棄用日期,並可用 deprecation link relation 指向說明;真正計畫停止 回應時再搭配 Sunset。棄用本身不應改變資源行為。以下日期只是本題範例,實際值要符合公開政策:
Deprecation: @1803859200
Sunset: Wed, 01 Sep 2027 00:00:00 GMT
Link: <https://api.example.com/migrations/orders-v2>; rel="deprecation"; type="text/html"關閉 v1 前需要同時滿足:支援義務已完成;已知關鍵使用者完成遷移或取得核准例外;剩餘流量能解釋; 遷移指南與支援管道可用;v2 的錯誤率、延遲和業務結果達到門檻;關閉演練能復原。若某個高價值客戶 尚未遷移,選擇延長、提供受限相容閘道或依合約處理,不能為了讓採用率看起來達到 100% 就忽略它。
復原也要在發布前定義。v2 路由和表示轉接器可以獨立關閉,領域寫入保持向後相容,v1 始終保留上一個 已驗證成品。若 v2 新欄位需要新儲存,先執行相容的擴充和回填,再讓 v2 讀取;不要讓 v2 發布同時 刪除 v1 所需資料。復原 v2 時恢復其實作版本,不能改變「v2」所代表的契約來掩蓋故障。
高品質示範回答
「我先把問題拆成契約變化和發布生命週期。這裡用戶端無法強制升級,所以我要同時保護 JSON 解析、 舊 SDK 執行,以及相同請求仍然回傳完整、含義相同的結果。
customer_name 從字串變成物件會改變型別,重新命名也等於刪除後新增;這兩項放到 v2。替 GET /orders 預設分頁會讓舊用戶端少拿資料,是語意破壞,也放到 v2。新增 refunded 不能直接 假設安全:如果回應列舉在契約中是開放的,而且舊 SDK 能保留未知值,可以在 v1 擴充;如果產生的 程式碼使用封閉列舉或使用者窮舉,我會留在 v2,或先替 v1 增加可辨識的後備行為並驗證。
我會保留 /v1/orders 的回應和完整清單語意,新建 /v2/orders 的結構化客戶物件、游標與頁面 規則。兩個版本共用查詢、授權與訂單狀態邏輯,只在請求解析和回應表示上分層。合併前比較 OpenAPI, 執行最後一個 v1 SDK 的契約測試,並重播去識別化請求以驗證狀態碼、排序、預設值和完整性。v2 先由 內部 SDK 和少量已知整合選擇加入,出現迴歸就關閉 v2 路由,v1 不受影響。
遷移期我會依應用程式 ID 統計活躍使用者,把總流量當成輔助,並追蹤版本採用、解析失敗、4xx、 5xx、 延遲、分頁完成率和關鍵帳戶。文件會列出欄位映射、分頁迴圈、列舉後備和測試環境。正式棄用時透過 回應標頭和遷移連結讓狀態可被發現,停止日期另行聲明。支援義務、關鍵使用者、剩餘流量和 v2 SLO 全部滿足後才關閉 v1,並先做可復原演練。這樣版本號、相容實作、遷移證據和最終下線屬於同一套 可驗證方案。」
常見錯誤
- 只回答在 URL 中加
/v2→ 沒有說明什麼變化會破壞誰,也沒有遷移和停止條件 → **先建立
v1 基線,逐項依原始碼、傳輸格式和語意分類。**
- 認為新增回應內容永遠相容 → 嚴格反序列化、封閉列舉和窮舉分支仍可能失敗 → **核對公開契約、
產生的 SDK,並用真實舊版本測試。**
- 讓 v1 自動轉到 v2 → 相同版本標記開始代表不同語意,用戶端無法選擇或復原 → **保留穩定 v1
表示層,讓使用者明確選擇 v2。**
- 替 v1 和 v2 複製整套服務 → 安全修正和業務規則容易漂移,雙版本成本快速擴大 → **共用領域
邏輯,只隔離真正不同的解析與表示。**
- 公告日期後直接停止服務 → 匿名長尾、低頻對帳和關鍵客戶可能仍依賴 v1 → **依使用者核實採用率、
支援義務和剩餘流量,並預演復原。**
- 只看伺服器端 2xx → 用戶端可能成功收包卻解析失敗、漏頁或誤解新狀態 → **增加舊 SDK、分頁
完成率、端對端業務結果和支援訊號。**
追問及應對
追問一:如果所有使用者都是同一公司的三個服務,還需要 v2 嗎?
未必。若能辨識所有呼叫方、協調發布並快速復原,可以採用擴充—遷移—收縮:先增加相容欄位或新 端點,發布能同時讀取新舊形狀的使用者,再切換提供者,確認舊使用為零後刪除舊契約。仍要保留契約 測試和版本化部署證據,但不必為可控使用者永久維護公開 v2。任何一個離線工作、舊用戶端或外部夥伴 無法協調時,這個前提就不成立。
追問二:Webhook 事件應該跟隨帳戶目前的 API 版本嗎?
不應在重播時使用「目前版本」重新解釋歷史事件。端點建立時固定事件 API 版本,事件本身記錄版本, 重試與重播保持原始形狀;升級時建立或切換到新版本端點並驗證使用者。Stripe 的公開文件也明確說明 Webhook 事件與端點建立時的 API 版本有關。雙發新舊事件會產生重複副作用,只有使用者具備穩定事件 ID 去重且遷移方案明確時才可短期使用。
追問三:新增一個回應列舉值到底算不算破壞性變更?
答案取決於已發布契約。GitHub 把新增列舉值列為增量變化;Google 的相容指引同時提醒,回應列舉的 舊使用者程式碼可能無法妥善處理新值。面試中應說明這個張力:若契約把列舉定義為開放集合,SDK 提供 unknown 表示且使用者必須容錯,可以按相容變化發布;若型別封閉或生態中有窮舉判斷,應按 破壞風險處理,先改善 SDK 與契約,或放到新版本。不能只憑伺服器端 schema 得出結論。
追問四:v1 無界清單已經造成逾時,等所有客戶遷移來不及怎麼辦?
先用現有契約允許的配額、快取、查詢最佳化和背壓恢復服務,並主動聯絡高流量使用者遷移到 v2。若 必須設定緊急回應上限,要明確它可能破壞 v1,走事故與變更核准,公告受影響範圍,提供批次匯出或 暫時相容管道,並監控漏資料風險。靜默回傳前 100 筆且仍報 200,會把可用性事故變成難發現的資料 錯誤,不能當作相容修正。
追問五:停止日期到了,仍有 0.2% 的 v1 流量怎麼辦?
先把比例還原成使用者和業務:是探針、錯誤設定、月底工作,還是合約客戶。能辨識且無業務依賴的 流量可以清理;關鍵使用者需要升級、例外或升級支援決策;匿名流量則依公開政策和風險處理。最終 決定應記錄剩餘呼叫、通知證據、支援義務、復原方案與負責人。百分比本身不能證明安全,也不能無限期 阻止版本退出。