仕組みから学ぶ Web
応用読了目安 14#REST#API設計#URL設計#バージョニング

リソース設計 — 一度公開したら変えられない契約

URL は動詞ではなく名詞で切る、の理由。そして「きれいな REST」に収まらない操作をどう扱うか。

この記事の進み方

What — リソースをどう切るか

REST の中心的な考え方は、操作ではなくモノに名前を付けることです。

Compare動詞で切る場合と、名詞で切る場合

操作ごとに URL を作る。増え続け、統一感がなくなる。

POST /getUser
POST /getUserList
POST /createUser
POST /updateUser
POST /updateUserEmail      ← 部分更新のたびに増える
POST /deleteUser
POST /searchUsersByName
POST /searchUsersByEmail   ← 条件ごとに増える

→ 命名規則が人によってぶれる
→ キャッシュもできない(全部 POST)
URL の数
操作の数だけ増える
命名の一貫性
保ちにくい
キャッシュ
効かせにくい
学習コスト
一覧を読む必要がある
  • ただし、複雑な業務操作には向いていることもある。後述。

動詞で切ると、操作が増えるたびに URL が増える。名詞で切ると、URL の数は概念の数に収まり、操作はメソッドで表現される。

HTTP メソッドの性質

GET
取得。安全(状態を変えない)で冪等。キャッシュできる。GET で状態を変えてはいけない。
POST
作成・任意の処理。冪等ではない。2回送ると2件できる。
PUT
指定した内容で全体を置き換える。冪等(何回送っても同じ状態になる)。
PATCH
一部だけを更新する。冪等とは限らない(差分の種類による)。
DELETE
削除。冪等(2回目は「すでに無い」だが、結果として状態は同じ)。
安全(safe)
サーバーの状態を変えない。GET / HEAD / OPTIONS。
冪等(idempotent)
何回実行しても結果の状態が同じ。GET / PUT / DELETE。

GET で状態を変えてはいけない理由は3つあります。

  1. ブラウザやプロキシが勝手に先読みすることがある
  2. キャッシュされるので、2回目が実行されない
  3. <img src="/delete?id=1"> のような形で CSRF が成立する

CRUD に収まらない操作

実務では、「注文を確定する」「パスワードをリセットする」のような、CRUD に収まらない操作が必ず出てきます。

❌ POST /confirmOrder          動詞の URL が増えていく
❌ PATCH /orders/123           { status: "confirmed" } ← 状態を直接書かせる
✅ POST /orders/123/confirmation   状態遷移を「モノ」として表す
✅ POST /orders/123/cancellation

3つ目の形は、「確定」という行為の記録をリソースとして作るという発想です。実際、確定日時や確定者を記録したいことが多いので、モノとして扱うのが自然です。

2つ目(PATCHstatus を直接書かせる)を避ける理由は、クライアントに状態遷移のルールを知らせてしまうためです。status: "shipped" を送れば発送済みにできる、という設計だと、サーバー側でルールを守りにくくなります。

確認 — ここまで読めたか

レスポンスのフィールドを1つ削除するのは、どういう変更ですか?

まず選ぶ(解答例は a〜d の記号で説明します)

Why — なぜ API は契約なのか

API とライブラリの内部関数の決定的な違いは、使い手を把握できるかです。

内部関数なら、リポジトリ全体を grep すれば呼び出し元が全部見つかります。シグネチャを変えても、コンパイルエラーで漏れが分かります。

API は違います。

  • クライアントは別のリポジトリ、別の会社、別の言語で動いている
  • モバイルアプリは古いバージョンが端末に残り続ける
  • バッチやスクリプトは、動いている間は誰も存在を思い出さない

変更したときに壊れる場所を、変更する側が把握できません。 だから API の変更は、内部リファクタリングとはまったく違う重さを持ちます。

後方互換を保つ変更・壊す変更

変更互換性理由
レスポンスにフィールドを追加✅ 保たれる知らないフィールドは無視される
リクエストに任意のパラメータを追加✅ 保たれる既存の呼び出しはそのまま動く
新しいエンドポイントを追加✅ 保たれる既存に影響しない
レスポンスからフィールドを削除❌ 壊れる冒頭の事故
フィールドの型を変える❌ 壊れるパースに失敗する
必須パラメータを追加❌ 壊れる既存の呼び出しが 400 になる
エラー時のステータスコードを変える❌ 壊れる分岐が変わる
列挙値を追加⚠️ 場合によるクライアントが未知の値を扱えるか次第

追加は安全、削除と変更は危険というのが基本形です。

バージョニングの方法

① URL パス      /v1/users    /v2/users
② ヘッダ        Accept: application/vnd.example.v2+json
③ クエリ        /users?version=2

実務では①が最も多いです。理由は単純で、

  • ブラウザで URL を開くだけで試せる
  • ログを見れば、どのバージョンが使われているか一目で分かる
  • ルーティングで分離しやすい

②のほうが「REST らしい」とされますが、デバッグのしやすさで①が選ばれます。

ただし、バージョンを増やすと保守対象が増えます。v1 と v2 を並行して動かすなら、バグ修正も両方に入れる必要がある。可能な限り、互換性を保つ変更で乗り切るほうが安上がりです。

確認 — ここまで読めたか

「記事を公開する」操作を API にするとき、どれが扱いやすいですか?

まず選ぶ(解答例は a〜d の記号で説明します)

演習 — まず自分で判断する

解説を読む前に、まず自分で判断してみてください。ここで一度詰まっておくと、 次の節の判断軸が「なるほど」ではなく「そう来たか」に変わります。

演習 — 設計判断を問う

この機能群の API をどう設計しますか?

与えられた条件
  • 社内の稟議申請システムに API を追加する
  • 必要な操作: 申請の作成・一覧・詳細取得・下書き保存・申請提出・承認・却下・差し戻し・取り下げ
  • 承認は複数段階(課長 → 部長 → 役員)で、誰がいつ承認したかを記録する必要がある
  • 却下と差し戻しには理由のコメントが必須
  • 外部の会計システムからも呼ばれる(先方のリリースサイクルは半年に1回)
  • 将来、申請種別ごとの承認フローのカスタマイズを検討している
この軸で考える
  • · どの操作が「モノ」として表現できるか
  • · 承認履歴を記録する必要があることは、設計にどう効くか
  • · 半年に1回しかリリースしない相手がいることは、何を意味するか
まず選ぶ(解答例は a〜d の記号で説明します)

演習 — 説明できるか

「使われていないフィールドなので消した」という変更を、なぜ危険かを説明してください

与えられた条件
  • 社内のコードには参照が無かった
  • デプロイ後に外部から3件の連絡が来た
  • 今後の進め方まで決めたい

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。

この記事を前提にしている記事