仕組み
冪等性は「べきとうせい」と読みます。HTTPでは、同一のリクエストを複数回送ったとき、サーバーに意図する効果が1回の場合と同じになる性質です。
PUTとDELETEは冪等なメソッドとして定義されています。POSTには、その保証がありません。ここで見るのはレスポンスの一致ではなく、要求した操作の効果です。HTTP仕様:冪等なメソッド
PUTが冪等である理由
PUTは、指定したリソースの状態を、送った内容で作成・置換する操作です。
たとえば、通知設定全体を次のように指定します。
PUT /users/42/notification-settings
Content-Type: application/json
{
"email": true,
"push": false
}
同じ要求を繰り返しても、設定は変わりません。
1回目 → email=true、push=false
2回目 → email=true、push=false
3回目 → email=true、push=false
一方、「現在の値を反転する」という処理をPUTに実装すると、呼ぶたびに状態が変わります。PUTと名付けるだけでは冪等にならず、実装もその意味に合わせる必要があります。
DELETEが冪等である理由
DELETE /articles/123
1回目で記事を削除し、2回目も「記事が存在しない」という状態になります。
1回目が 204 No Content、2回目が 404 Not Found でも、冪等性と矛盾しません。レスポンスは違っても、削除という目的は同じ状態に到達しています。
POSTが冪等と保証されない理由
POST /orders
Content-Type: application/json
{
"productId": "book-1",
"quantity": 1
}
このAPIが「呼ばれるたびに新しい注文を作る」仕様なら、2回送ると注文が2件できます。
問題になるのは、次の通信障害です。
クライアント → 注文作成を送信
サーバー → 注文作成に成功
通信 → 応答が途中で失われる
クライアント → 成功したか分からず再送
サーバー → もう1件作成してしまう
POSTでも重複実行を防げる
代表的な方法が、操作単位の識別子を送る Idempotency-Key です。
POST /orders
Idempotency-Key: 7e24b712-...
サーバー側で、そのキーに対応する実行状態や結果を記録します。同じ操作の再送なら、新しく注文を作らず、既存の結果を返します。Stripeもこの方式を提供しています。Stripe:冪等なリクエスト
実装では、次の条件が重要です。
- 同じ操作の再送には、同じキーを使う。
- 別の注文には、新しいキーを使う。
- 同じキーで異なる内容が来たら、拒否する。
- 同時に2件届いても、処理を重複開始しないようにする。
- キーの保存期間と、その期間を過ぎた再送の扱いを決める。
「実行済みか確認してから作成する」だけでは、同時リクエストが両方とも確認を通過できます。DBの一意制約やトランザクションなどで、競合も防ぐ必要があります。
設計上の要点
通信が失敗しても、処理自体は成功している可能性があります。再送を前提に設計することが重要です。