REST API設計の基礎と冪等性 - HTTPメソッドの意味と Idempotency-Key

REST API設計の基礎と冪等性 - HTTPメソッドの意味と Idempotency-Key

作成日:
読了:10
更新日:

POST を2回送ったら二重に課金された」——API設計で冪等性(idempotency)を押さえていないと起きる事故です。REST API は、HTTPメソッドの意味を正しく使うだけで、安全で予測可能になります。この記事では、リソース設計とメソッドの性質、そして冪等性を中心に、RFC 9110・MDN・Stripe を一次ソースに整理します。

RESTの基本

REST は Roy Fielding が2000年の博士論文で示したアーキテクチャスタイルで、要点はこうです。

  • リソース指向: API は「操作(動詞)」ではなく「リソース(名詞)」を中心に設計する
  • ステートレス: 各リクエストは自己完結(サーバーにセッション状態を持たせない)
  • 統一インターフェース: HTTPメソッド + URI でリソースを操作する

「何をするか」はメソッドが担い、URL は「どのリソースか」だけを表す——これが軸です。

URL設計: 良い例と悪い例

良い例: 名詞・複数形・階層、操作はメソッドで
GET    /users            # 一覧
POST   /users            # 作成
GET    /users/123        # 取得
PUT    /users/123        # 全体置換
PATCH  /users/123        # 部分更新
DELETE /users/123        # 削除
GET    /users/123/orders # ユーザーの注文一覧
 
# フィルタ・ソート・ページングはクエリで
GET    /articles?status=published&sort=-date&limit=20&offset=0
悪い例: URLに動詞、メソッドと矛盾、深すぎるネスト
GET  /getUsers
POST /deleteUser/123
POST /articles/123/update
/api/articles/1/comments/2/likes/3/reactions

URLに get / create / delete などの動詞を入れないのが基本です(それはメソッドの仕事)。

HTTPメソッドの2つの性質: safe と idempotent

RFC 9110 は、メソッドに2つの性質を定義しています。

  • safe(安全): 意味として読み取り専用で、サーバー状態を変えない
  • idempotent(冪等): 同じリクエストを何回送っても、サーバーへの効果が1回と同じ
メソッドsafeidempotent主な用途
GET / HEADYESYES取得
OPTIONSYESYES対応メソッド確認
PUTNOYES完全置換
DELETENOYES削除
POSTNONO作成・アクション
PATCHNONO(原則)部分更新
  • safe なら必ず idempotent(逆は成り立たない)
  • PUT / DELETE は冪等。「同じ置換」「同じ削除」を繰り返しても最終状態は同じだから
  • POST は非冪等。送るたびに新しいリソースができる
  • PATCH は原則どちらでもない(差分やカウンタ加算は繰り返すと結果が変わる)

NOTE

DELETE の2回目が404でも冪等です。 冪等性は「サーバー状態の変化」の話で、返ってくるステータスコードの一致ではありません。「削除済み」という状態が変わらなければ冪等です。

なぜ冪等性が重要か

ネットワークは不確実です。レスポンスが返る前にタイムアウトしたとき、クライアントは「成功したのか失敗したのか」分かりません。安全にリトライできるかは、メソッドが冪等かどうかで決まります。

  • GET / PUT / DELETE: そのまま再送して安全
  • POST: そのまま再送すると二重作成・二重課金のリスク

この「POST を安全にリトライしたい」を解決するのが、次の Idempotency-Key です。

POST を安全にする: Idempotency-Key

Stripe などが採用する仕組みで、クライアントが一意なキーを送り、サーバーが結果を記録します。同じキーの再送には最初の結果をそのまま返すため、二重実行を防げます。

Idempotency-Key 付きの POST
POST /v1/charges HTTP/1.1
Host: api.stripe.com
Authorization: Bearer sk_live_xxxx
Idempotency-Key: a8098c1a-f86e-11da-bd1a-00112444be1e
Content-Type: application/x-www-form-urlencoded
 
amount=2000&currency=jpy&source=tok_xxxx
  • キーは V4 UUID など一意な値(最大255文字、個人情報を含めない)
  • サーバーは結果を一定期間キャッシュ(Stripe は24時間以上)。期限切れ後は新規扱い
  • バリデーションエラーや並行衝突はキャッシュされず、安全に再試行できる
  • GET / DELETE元から冪等なので不要

WARNING

「Idempotency-Key を付ければ POST が冪等になる」は不正確です。これはリトライを安全にする仕組みであって、メソッド自体の性質を変えるわけではありません。サーバー側の実装(キーの記録と再送時の同一レスポンス)があって初めて機能します。

ステータスコードの使い分け(要点)

コード場面
200 OK取得・更新成功(ボディあり)
201 Created作成成功。Location ヘッダで新リソースのURIを示す
202 Accepted非同期処理を受理(結果は未確定)
204 No Content成功・ボディ不要(DELETE や一部の PUT

詳しい使い分けはHTTPステータスコードの実践的な使い分けを参照してください。

エラーレスポンスは RFC 9457 で揃える

エラーの返し方は、独自JSONより標準形式(RFC 9457 / application/problem+jsonに寄せると、クライアントが扱いやすくなります。

application/problem+json(RFC 9457)
{
  "type": "https://example.com/errors/out-of-credit",
  "title": "残高が不足しています",
  "status": 403,
  "detail": "現在の残高は30ですが、この操作には50必要です",
  "instance": "/account/12345/msgs/abc"
}

周辺の設計トピック

  • バージョニング: URLパス方式(/v1/users)が主流で明示的。ヘッダ方式(Accept のメディアタイプ)はURLが綺麗だが管理が複雑
  • ページネーション: 小規模は offset で十分。大規模では OFFSET 100000 がスキャンを増やして遅くなるためカーソル方式が有利
  • 認証・横断的関心事: トークンはJWT、ブラウザからの呼び出しはCORSも合わせて設計する

よくある誤解

  • 「PATCH は部分更新だから安全」: 誤り。PATCH は safe でも idempotent でもない
  • 「PUT と PATCH は同じ」: PUT完全置換(送らない項目は消えうる)、PATCH部分更新
  • 「REST = HTTP」: REST はアーキテクチャスタイル、HTTP はその実現手段(実務上はほぼ同義に使われる)
  • 「DELETE は2回目も200であるべき」: 404でも冪等。状態が変わらなければよい

まとめ

  • REST はリソース(名詞)中心。操作はHTTPメソッドが担う(URLに動詞を入れない)
  • safe=読み取り専用、idempotent=何回でも結果が同じGET/PUT/DELETE は冪等、POST/PATCH は非冪等
  • 冪等性は安全なリトライの土台。POSTIdempotency-Key + サーバー実装で二重実行を防ぐ
  • 作成は 201Location、非同期は 202、本文不要は 204。エラーは RFC 9457 で標準化
  • バージョニング・ページネーション(カーソル)・認証も設計時に併せて考える

メソッドの意味と冪等性を押さえるだけで、API は「壊れにくく、リトライしても安全」になります。設計の8割はここで決まります。

参考リンク

HTTP ステータスコードの実践的な使い分け - 401 と 403、3xx の違いをちゃんと選ぶ

HTTP ステータスコードの実践的な使い分け - 401 と 403、3xx の違いをちゃんと選ぶ

9

HTTP ステータスコードを、API 設計で迷いやすいポイントを中心に整理します。2xx/3xx/4xx/5xx の意味と使い分け、401 と 403 の違い、301/302/307/308 のメソッド保持、200 でエラーを返すアンチパターン、404 と 410、429 と Retry-After、422 の位置づけまで、RFC 9110 や MDN といった一次ソースをもとにまとめます。

Cookie・セッション・SameSite の基礎 - Secure / HttpOnly と CSRF・CORS の関係

Cookie・セッション・SameSite の基礎 - Secure / HttpOnly と CSRF・CORS の関係

11

Web認証の土台となる Cookie・セッション・SameSite を実務目線で整理します。Set-Cookie の属性(Expires/Max-Age・Domain/Path)、Secure と HttpOnly が防ぐ脅威、SameSite の Strict/Lax/None の違いと None に Secure が要る理由、サーバー側セッションとログイン後の ID 再生成、SameSite だけで CSRF を防ぎきれない理由、クロスオリジンで Cookie を送る CORS の条件、__Host-/__Secure- プレフィックスまで、MDN・RFC 6265bis・OWASP を一次ソースにまとめます。

CORS の仕組みとハマりどころ - プリフライト・credentials・Allow-Origin を理解する

CORS の仕組みとハマりどころ - プリフライト・credentials・Allow-Origin を理解する

10

CORS(オリジン間リソース共有)を実務目線で整理します。同一オリジンポリシーとの関係、単純リクエストとプリフライト(OPTIONS)の条件、Access-Control-Allow-Origin などの各ヘッダ、credentials 付きで * が使えない理由、Vary: Origin とキャッシュ、そして「No Access-Control-Allow-Origin header」エラーの意味と対処まで、MDN と WHATWG Fetch Standard を一次ソースにまとめます。CORS はブラウザの仕組みであって認可ではない、という勘所も。