題幹與適用場景
你負責一個團隊資料 API。用戶端可能只修改 displayName,也可能提交完整資料;行動端網路不穩定會重試。請解釋何時使用 PUT 或 PATCH,如何處理欄位缺漏與 null、並發衝突、原子性、冪等重試與相容性。
Greenroom 的 2026 後端面試題單把 PUT 與 PATCH 的差異、冪等性與方法選擇列為 API 題;RFC 5789 規定 PUT 的請求實體代表資源的新完整版本,而 PATCH 的實體是一組套用到目前資源的修改指令。本文不綁定特定公司。
面試官考察點
普通回答只背「PUT 全量、PATCH 增量」。強回答會先定義資源契約,再說明缺漏欄位是否保留原值、null 是否清除欄位、更新是否需要 If-Match,以及失敗時是否保證整份變更都不生效。面試官還會追問重複請求、回應遺失、未知欄位、稽核事件與舊用戶端。
核心訊號是能把 HTTP 方法語意落到資料庫更新與並發控制,而不是把 PATCH 當成「更省流量的 PUT」。
回答前需要釐清的問題
- PUT 是否允許建立? 本題只更新既有團隊資料;若允許以穩定 URI 建立,要明確資源所有權與重複請求語意。
- 用戶端送的是完整資源還是變更文件? 完整資源用 PUT;欄位操作或部分表示用 PATCH。
- 缺漏欄位與
null分別代表什麼? 本題約定 PATCH 中缺漏代表保留原值,null代表清除可空欄位;不可空欄位拒絕。 - 並發更新能否覆蓋? 本題不接受靜默覆蓋,要求
ETag/If-Match或資料庫版本條件。 - 是否需要觸發副作用? 資料更新後的搜尋索引、稽核事件與通知必須與成功狀態一致,非同步副作用不能假裝屬於 HTTP 原子性。
30 秒回答框架
「我把 PUT 定義為用完整表示取代資源,用於用戶端擁有全量快照的場景;PATCH 傳遞部分修改,用於只改 displayName 這類操作。PATCH 必須明確缺漏與 null 的含義,並在資源版本不匹配時回傳衝突,避免舊表單覆蓋新資料。伺服器在一個資料庫交易中原子套用變更,成功後發出帶版本的非同步事件;用戶端用相同的可重試請求語意與 If-Match 重試。若變更是命令而非資源修改,我會另設動作端點,不濫用 PATCH。」
分步驟深入解答
第一步:把兩種方法寫成資源契約
| 維度 | PUT | PATCH |
|---|---|---|
| 請求含義 | 請求體是資源的新完整表示 | 請求體是套用到目前資源的修改指令或部分表示 |
| 未出現欄位 | 通常代表用戶端提供完整狀態,不應靜默保留舊欄位 | 必須明確是保留原值還是非法 |
| 冪等性 | 相同表示重複套用應得到同一資源狀態 | 規範不保證冪等,但特定 PATCH 文件可以設計成冪等 |
| 典型場景 | 同步完整編輯器快照、取代設定 | 修改單一欄位、JSON Merge Patch 或 JSON Patch |
PUT 的關鍵不是「請求體較大」,而是用戶端宣告了資源完整狀態。若用戶端只知道部分欄位卻送 PUT,伺服器可能把未攜帶欄位視為刪除或預設值。PATCH 的關鍵不是「永遠安全」,它仍可能觸發驗證、權限與其他資源副作用。
第二步:明確 PATCH 文件與欄位三態
PATCH /v1/teams/t_123/profile HTTP/1.1
Content-Type: application/merge-patch+json
If-Match: "profile-v17"
{"displayName":"Design Platform","avatarUrl":null}本題採用 JSON Merge Patch 風格:displayName 被取代,avatarUrl: null 清除可空欄位,未出現欄位保留不變。若業務需要陣列元素級操作、移動或條件測試,改用 JSON Patch 風格的操作列表,並限制允許的路徑。
伺服器先解析文件,再按欄位白名單、型別、長度、權限與業務不變量驗證;禁止把用戶端任意 JSON 路徑直接映射到資料庫欄位。未知欄位可以嚴格拒絕,也可以在版本契約中忽略,但必須固定寫入文件。
第三步:用版本條件阻止靜默覆蓋
GET /v1/teams/t_123/profile HTTP/1.1
ETag: "profile-v17"
PATCH /v1/teams/t_123/profile HTTP/1.1
If-Match: "profile-v17"
Content-Type: application/merge-patch+json
{"displayName":"Design Platform"}資料庫更新帶版本條件:只有目前版本仍為 17 才寫入新值並遞增到 18。條件不滿足回傳 412 Precondition Failed,用戶端重新讀取、顯示衝突或重新產生補丁;不能把舊版本強行覆蓋。若請求缺少必需的 If-Match,可以用 428 Precondition Required 強制呼叫方宣告並發策略。
PATCH 還必須整份原子套用:欄位 A 成功、欄位 B 失敗時不能留下半個變更。資料庫交易、驗證階段與唯一約束共同決定是否提交;副作用事件應在提交後由 outbox 或等價機制非同步發布,並攜帶資源版本。
第四步:處理重複請求與未知結果
PUT 的重複請求只要完整表示相同,最終資源狀態相同。PATCH 只有在操作本身可重複時才有同樣性質:設定 displayName 是冪等的,increment seats by 1 不是。非冪等 PATCH 需要請求 ID、條件版本或改成表達目標狀態的操作。
網路中斷時,用戶端不知道伺服器是否已提交。用戶端應使用穩定請求 ID,伺服器記錄請求指紋與結果,或用 If-Match 與目標狀態重試;不能無條件重放會追加副作用的補丁。伺服器回傳成功後再由事件消費者處理索引與通知,重複事件由事件 ID 去重。
第五步:決定何時不用 PUT/PATCH
「發布團隊資料」「重新計算權限」「發送邀請」是命令,不是資源表示的取代或局部修改。為它們設計 POST /profile:publish 等動作端點,更能表達權限、稽核、重試與非同步狀態。若把命令偽裝成 PATCH,用戶端會誤以為只是在修改欄位,難以理解重複執行與副作用。
高競爭或跨資源交易也可能更適合領域命令:例如把成員角色從 editor 變成 owner 需要檢查名額與稽核規則,單純 PATCH 一個字串不足以表達業務不變量。
高品質示範回答
「我會同時提供 PUT 和 PATCH,但讓它們承擔不同契約。PUT 接受完整的團隊資料表示,用戶端省略欄位屬於契約錯誤或明確預設值,不會被當成『保持不變』。PATCH 接受受限的 Merge Patch,只允許修改白名單欄位;缺漏欄位保持不變,null 只對可空欄位表示清除。
「兩者都要配合 ETag 與 If-Match。資料庫用版本條件更新,版本不匹配回傳 412,避免舊用戶端覆蓋新資料。PATCH 文件先完整驗證,再在單一交易中套用;提交後透過 outbox 發布帶版本的索引事件。設定欄位的 PATCH 可以冪等,遞增這類操作必須使用請求 ID、條件版本或改成『設定目標值』。發布、邀請等有明顯副作用的動作用 POST 動作端點。最後我會測試重複請求、回應遺失、欄位缺漏/null、並發衝突、未知欄位、部分失敗與舊用戶端相容。」
常見錯誤
- 錯誤表現 → 把 PUT 解釋成「更新任意幾個欄位」 → 失敗原因 → 呼叫方無法知道遺漏欄位是否會被清除 → 修正方法 → 把 PUT 契約固定為完整表示,局部修改使用 PATCH。
- 錯誤表現 → 說 PATCH 天生冪等 → 失敗原因 → RFC 5789 不保證 PATCH 冪等,增量操作重複會改變結果 → 修正方法 → 只把設定目標狀態的補丁視為可重複,其他操作增加條件或請求去重。
- 錯誤表現 → 直接把 JSON 鍵映射到資料庫欄位 → 失敗原因 → 繞過欄位權限、型別驗證與跨欄位不變量 → 修正方法 → 使用欄位白名單和明確領域驗證。
- 錯誤表現 → 版本衝突時最後寫入獲勝 → 失敗原因 → 舊表單會靜默覆蓋新資料 → 修正方法 → 使用
If-Match與版本條件,衝突回傳 412。 - 錯誤表現 → 資料庫提交後立刻同步呼叫搜尋服務 → 失敗原因 → 回應遺失或程序崩潰會讓狀態與索引分叉 → 修正方法 → 交易內寫 outbox,提交後非同步投遞並按事件 ID 去重。
追問及應對
如果產品要求 PATCH 缺漏欄位也清除,怎麼辦?
這會把 PATCH 變成另一種完整表示契約,容易與 PUT 混淆。可以改用 PUT,或明確採用「欄位集合取代」語意並單獨命名媒體類型;關鍵是讓用戶端知道未送欄位的後果。
如果兩個用戶端都基於版本 17 修改不同欄位呢?
預設仍回傳一個 412,讓用戶端合併後重試,因為伺服器無法自動證明兩項修改不會衝突。只有業務明確允許欄位級合併,才能按欄位版本或 JSON Patch 的測試操作安全合併。
如果 PATCH 觸發計費或通知怎麼辦?
把資源更新與副作用分成兩個可稽核步驟:資源交易提交後寫 outbox,消費者按事件 ID 冪等處理。若副作用本身必須由使用者明確觸發,改成獨立 POST 命令並回傳非同步操作狀態。