はじめに
「リソースに対するCRUD」というREST本来の設計思想は美しい一方、実務のAPIではCRUDだけでは表現しきれない操作が必ず出てきます。無理にCRUDへ押し込めようとすると、バリデーションの置き場が曖昧になったり、どのリソースのエンドポイントか判断できなくなったりします。
本記事では、REST設計で頻繁に遭遇する「表現しにくい操作」を4パターンに整理し、それぞれの解決策を紹介します。
パターン1: 状態遷移に条件がある
ステータスを直接書き換える設計は、一見シンプルですが問題があります。
// ❌ ステータスを直接書き換える
PUT /orders/1
{ "status": "CANCELLED" }
オーダーの状態は、発送後はキャンセル不可など、各状態ごとにビジネスルールを持ちます。一つのエンドポイントに押し込めると実装が複雑になり、保守性が悪くなります。
// ✅ アクションとして表現する
POST /orders/1/cancel
POST /orders/1/ship
POST /orders/1/complete
アクションエンドポイントにすることで、「キャンセル」という操作が持つビジネスルール(発送済みならエラーにする、在庫を戻す、通知を送る、など)をサーバ側のサービス層に明確に閉じ込められます。
パターン2: トランザクションをまたぐ操作
送金のように、複数のリソースにまたがる操作はCRUDに収まりません。
// 送金: 送り元の残高を減らして受け取り側を増やす
// → どちらのリソースのエンドポイントか判断できない
/accounts/1 を更新するのか /accounts/2 を更新するのか、どちらのエンドポイントに操作を寄せても不自然です。この場合は操作そのものを独立したリソースとして切り出します。
POST /transfers
{ "fromAccountId": 1, "toAccountId": 2, "amount": 5000 }
「送金」という行為自体を1つのリソース(transfer)とみなすことで、どちらのアカウントにも属さない中立な設計になります。
パターン3: 取得と同時にデータが変わる操作
GETはデータを変更しないことが原則です。しかし「一覧を取得すると同時に既読にする」といった要件は頻繁に発生します。
// ❌ GETでデータを変更する
GET /notifications // 取得と同時に既読にする
一見便利ですが、ブラウザのプリフェッチやリトライ、キャッシュの影響で意図しないタイミングで既読化されるリスクがあります。取得と更新は必ず分離します。
// ✅ 操作を分離する
GET /notifications
POST /notifications/read-all
パターン4: 複数リソースにまたがる一括取得
画面表示に必要なデータが複数リソースをまたぐ場合、REST原則に忠実だと複数回のリクエストが必要になります。
GET /orders/1
GET /products/5
GET /customers/3
GET /shipments/1
モバイル環境やレイテンシが問題になる場面では、これは無視できないコストです。画面専用のエンドポイント(BFF的な発想)を用意することで解決します。
GET /orders/1/detail // 必要なものをまとめて返す
厳密なリソース指向からは外れますが、「1画面1リクエスト」で済むメリットの方が大きい場面は多くあります。データの取得元が多岐にわたる場合はGraphQLの採用も選択肢に入ります。
まとめ
REST原則は「チームで一貫したルールを定める」ための道具であり、教条的に守ること自体が目的ではありません。状態遷移・トランザクション・取得と同時にデータが変わる操作・複数リソースの集約という4つの典型パターンに対して、アクションエンドポイントや専用エンドポイントという「逃げ道」をあらかじめチームで合意しておくことが、後から場当たり的な設計判断が積み重なるのを防ぎます。