如何設計冪等且具版本控制的 REST API

Backend2026-09-17TryQuickToolBox

你部署了一項新功能,突然間你的 API 收到來自客戶端在逾時後重試所發出的重複 POST 請求。你的資料庫現在有兩筆完全相同的訂單。或者你推出了一項破壞性變更,行動應用程式因為預期舊的回應格式而崩潰。這是 API 設計中最常見且最令人頭痛的兩個問題:非冪等操作和破壞性變更。本指南將展示如何透過冪等性和版本控制的實用模式來解決這兩個問題。

為什麼冪等性在 REST API 中很重要

冪等性意味著多次發出相同的請求與發出一次具有相同的效果。HTTP 方法有定義好的冪等語義:GET、HEAD、PUT、DELETE 和 OPTIONS 是冪等的,而 POST 和 PATCH 則不是。但現實世界的 API 通常需要接受 POST 來執行建立資源或處理付款等操作。網路故障、客戶端逾時和重試邏輯都可能導致重複。如果沒有冪等性,你將面臨重複扣款、重複記錄或狀態不一致的風險。

實現冪等 REST API 的技術

1. 為 POST 請求使用冪等鍵

最常見的做法是讓客戶端在 Idempotency-Key 標頭中發送一個唯一鍵(例如 UUID)。伺服器儲存該鍵和操作的結果。如果再次收到相同的鍵,伺服器會回傳已儲存的結果,而不是重新執行操作。

POST /payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "amount": 1000,
  "currency": "USD"
}

實作步驟:

  1. 客戶端為每個邏輯操作產生一個唯一鍵(UUID v4)。
  2. 伺服器檢查該鍵是否存在於持久化儲存中(例如 Redis 或資料庫)。
  3. 如果不存在,處理請求並將該鍵與回應狀態和內容一起儲存。
  4. 如果存在,直接回傳已儲存的回應,不再重新處理。

設定合理的過期時間(例如 24 小時)以避免儲存空間無限增長。

2. 利用條件式請求

對於更新操作,使用 ETag 和 If-Match 標頭來防止更新遺失。伺服器回傳一個代表當前狀態的 ETag。客戶端透過 If-Match 將其送回;如果資源已變更,伺服器會回傳 412 Precondition Failed。這讓 PUT 請求在面對競爭條件時更加安全。

PUT /articles/123
If-Match: "abc123"
Content-Type: application/json

{"title": "Updated title"}

3. 設計具天然冪等性的 PUT

當客戶端能決定資源 URL 時,優先使用 PUT 而非 POST。例如,PUT /users/{userId} 天然就是冪等的,因為重複呼叫會覆寫同一個資源。只有在伺服器負責指派 ID 時才使用 POST。

4. 使用唯一約束處理重複偵測

將冪等鍵與業務鍵(例如訂單編號)上的資料庫唯一約束結合使用。如果有重複漏網,資料庫會拒絕它,你可以回傳 409 Conflict 並附上清楚的訊息。

REST API 的版本控制策略

API 會演進。新增欄位、變更行為或移除端點都可能破壞現有客戶端。版本控制讓你可以在不干擾它們的情況下引入變更。以下是幾種常見策略:

策略 範例 優點 缺點
URI 路徑 /v1/users 明確、易於路由、對快取友善 URL 會變動,可能顯得雜亂
查詢參數 /users?version=1 簡單、可選 容易被省略,不符合 REST 風格
自訂標頭 Accept-Version: v1 保持 URL 簡潔 較難在瀏覽器中測試
媒體類型 Accept: application/vnd.api.v1+json 內容協商,純 REST 複雜,較不常見

對大多數團隊而言,URI 路徑版本控制是最務實的選擇。它顯而易見、易於撰寫文件,並且能與 API 閘道和 CDN 良好配合。

如何在不破壞客戶端的情況下進行版本控制

  1. 從第一天就在路徑中加入 v1。之後再加入版本控制會更困難。
  2. 同一版本內僅做增量變更:新增選用欄位、新增端點。絕不移除或重新命名欄位。
  3. 優雅地棄用:公告終止支援、提供遷移指南,並監控舊版本的使用情況。
  4. 使用 sunset 標頭:Sunset: Sat, 31 Dec 2025 23:59:59 GMT 來通知客戶端。
  5. 最多維持兩個活躍版本以限制維護負擔。

整合應用:一個實務範例

考慮一個付款 API。你想要冪等地建立付款並為端點進行版本控制。

POST /v1/payments
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "amount": 1000,
  "currency": "USD"
}

伺服器邏輯:

對於更新操作,使用帶有 ETag 的 PUT:

PUT /v1/payments/123
If-Match: "xyz789"
Content-Type: application/json

{"amount": 1500}

如果 ETag 不相符,回傳 412。這可防止覆寫並行變更。

常見陷阱及如何避免

常見問題

什麼是冪等 REST API?

冪等 REST API 確保多次發出相同的請求所產生的結果與發出一次相同。這對於安全地處理重試至關重要,尤其是對於 POST 等非冪等方法。

哪些 HTTP 方法是冪等的?

GET、HEAD、PUT、DELETE、OPTIONS 和 TRACE 是冪等的。POST 和 PATCH 預設不是冪等的,但你可以使用冪等鍵等技術讓它們變成冪等。

為 REST API 進行版本控制的最佳方式是什麼?

URI 路徑版本控制(例如 /v1/users)是最常見且實用的做法。它明確、易於路由,並且能與快取和 API 閘道良好配合。其他選項包括查詢參數、自訂標頭和媒體類型,但它們各有取捨。

準備好測試你 API 的 JSON 回應了嗎?使用我們的 JSON Formatter 來快速驗證和美化輸出內容。