#140 RESTful API 設計〜原則を踏まえた実践的アプローチ
はじめに
最近、RESTの原則に基づいた設計や実装を強く意識する場面があり、実務的な視点から「あえて」RESTの基本原則から逸脱させているケースについても、改めて考えてみる良い機会となりました。
RESTの原則に基づくAPI設計は「リソース指向」であり、GETやPOST、DELETEなどのHTTPリクエストメソッドを、目的に合わせて適切に使用する形を理想としています。
しかし、実務では「可読性」「設計の一貫性」「バグ防止」などに重点をおいた結果、RESTの原則から逸脱した設計とする方が適していると判断されるケースもあります。
本記事では、前提知識としてRESTful APIについて概要を確認した後、RESTの原則から逸脱したケースをいくつか例に挙げ、その方針を採用する意図について考えていきたいと思います。
RESTful APIの基本
RESTの基本原則
RESTは、以下の6つの原則を基に設計されるリソース指向のアーキテクチャスタイルです。
- クライアントとサーバーの分離
- ステートレス
- キャッシュ
- 統一したインターフェース
- 階層システム
- オンデマンドのコード(※オプショナルな制約)
これらの詳細については、本題から逸れるため割愛させていただきます。
※詳しく知りたい方は、こちらの記事が参考になるかと思います。
なお、RESTでは「リソース」に対して操作を行うため、エンドポイントは名詞ベースになりますが、 RPCのようなスタイルでは「操作」自体に注目するため、関数のように動詞はじまりでエンドポイントを定義するのが一般的です。
実務で起こりうる議論
この原則に厳密に従おうとした場合に実務で起こりうる議論について、以下のようにいくつか考えてみました。
- 「論理削除はDELETEではなく、POSTまたはPATCHを使うべきか?」
- 「複雑な検索条件のAPIでもGETでパラメータを渡すべきか、POSTでリクエストボディに入れるべきか?」
- 「ログインAPI (
POST /login) は動詞はじまりだけど、REST的にはアリなのか?」 - (これらを受けて)「変更系リクエストをすべてPOSTに統一する選択肢は取り得るか?」
以降では、これらをテーマにRESTの原則と実務目線での設計判断をそれぞれ比較してみたいと思います。
RESTの原則と実務の設計判断の対比
(1)論理削除はDELETE 、POST 、PATCHのどれを使用するべきか?
REST原則例:DELETE を使う
DELETE /potatoes/{id}
- 操作として直感的であるものの、論理削除かどうかの判断はエンドポイントだけでは困難
- ただし、実際はデータを削除せず「非アクティブ化」するだけなので、DELETEの本来の意味合いである「リソースの削除」からは逸脱する
実務の設計例:POSTまたはPATCHを使う
POST /potatoes/{id}/deactivate
または
PATCH /potatoes/{id}/deactivate
- 「非アクティブ化する」というアクションを明示しているため、論理削除であることが理解しやすい
- ただし、動詞はじまりのエンドポイントを許容することになる
POSTとPATCHはどちらを使うべきか?
HTTPリクエストメソッドの使い方から考えると、delete_flag や deleted_at などの論理削除の状態を管理するカラムを変更するだけなので、リソースの部分更新を行うPATCHを使用する方がRESTの原則に沿っていると言えます。
しかし、論理削除で変更する値が決まっていてリクエストに必ずしもフィールドを含める必要がない場合、不要なバグの発生を回避するためにPOSTで論理削除を実行した方が良いと判断されることもあります。
PATCHを利用するケース
- 論理削除の処理で対象となるカラムを明示したい場合
- 例)
deleted_atカラムに対して、未来日などクライアント側で任意の日時での更新を可能とする場合
- 例)
- 論理削除であることをエンドポイントから読み取りやすくしつつ、RESTの原則に沿う方針とする場合
- ※PATCHは一般的に使用されるものの、一部の古いHTTPクライアントなどでは制限されている可能性があるため、互換性を確認する必要がある
POSTを利用するケース
- 論理削除を実施するトリガーとして利用する場合(更新するカラムの値が決まっている)
- 例)
deleted_atカラムの更新を現在日時で固定する場合 - 例)
delete_flagカラムを true に更新する場合
- 例)
- クライアント側からの意図しないパラメータ送信を回避したい場合
- 例)リクエストボディを空で送信して、論理削除のトリガーとする
- すべての環境での動作を保証したい場合
- ※PATCHでは、CORSのプリフライトリクエストやAPI Gatewayの権限制御などで適切な設定が必要となる
上記から、POSTを利用することで、クライアント側の実装がシンプルになるという利点もありますね。
また、PATCHを利用するとリソースの部分更新の意味合いが強いですが、単に「論理削除アクション」という扱いをしたいのであればPOSTの方が適していると言えます。
このように、物理削除と論理削除を明示的に分ける方針とした結果、DELETEではなくPOSTやPATCHを選択するケースもあります。
(2)「複雑な検索」はGETよりPOSTを使うべきか?
REST原則例: GETを使用する
- リソース指向の統一性が保たれる
- キャッシュを利用できる
- クエリパラメータが増えるとURLが長くなりすぎる(場合によっては最大URL長を超過する可能性も)
- JSON 形式でリクエストを送れないため、クライアント側で検索条件を可変とするような複雑なフィルタリング条件だと表現しにくい
実務の設計例:POSTを使用する
- リクエストボディを使用して、JSON形式でリクエストを送信できる(複雑な条件の表現が容易)
- 最大URL長の制限を回避できる
- キャッシュが利用できないため、GETと比べてパフォーマンスが落ちる
- 同じリクエストでも、都度サーバー処理が発生する
- エンドポイントから処理内容が読み取りにくく、直感的でないため可読性が低い
特に複雑な条件がなかったとしても、日本語のフリーテキストでの検索を許容している場合、GETだとURLエンコードする必要があるがあるため、最大URL長を意識しなくてはなりませんね。
POSTであれば、そもそもURLエンコードも不要となるので、そういった場面でもPOSTの使用を検討するケースが多いかと思います。
また、検索の自由度が高くなりすぎる場合や、複雑なデータ取得が求められる場合、GraphQLを導入するという選択肢もあります。
クライアント側が必要な情報のみを柔軟に取得できるようになるため、POSTの代替手段としても考えられます。
こちらについては次回以降の記事で取り上げようと思いますので、今回は触れる程度に留めさせていただきます。
(3)ログインAPI( POST /login )はREST的にアリなのか?
REST原則例: POST /session を使用する
- リソースに対する操作として、名詞はじまりのエンドポイントで定義できる
- ただし、クライアント側の情報を管理していることになるため、REST原則「ステートレス」に違反している形ではある
実務の設計例: POST /login を(許容して)使用する
- 例外的に、動詞はじまりのエンドポイントを許容する形になる
- 直感的にログイン処理を扱っていることがわかりやすく、可読性が高い(
POST /sessionは「セッションの作成」とも読み取れる) -
POST /loginは多く採用されており、一般的な設計方針として広く認知されている
なぜ実務では POST /login が許容されるのか
RESTful APIとして設計していたとしても、POST /login を許容しているケースも多く見受けられます。
理由としては、以下の項目が考えられます。
- サーバー側でセッションを保持するようなログイン処理では、それ自体がクライアント側の情報管理という側面が強く、「ステートレス」の原則に違反している
- ※JWTなどのトークンベースの認証であれば、ステートレスの原則に沿った実装が可能
- リソース指向のみを厳守しても、「ステートレス」の原則を満たすような本質的な問題の解決には至らない
- 可読性や認知度の観点から
POST /loginの方が直感的に理解しやすい
このように、RESTの原則を意識しつつも、可読性や認知度を考慮した結果、あえて原則から逸脱した設計としているケースとなっています。
(4)変更系リクエストをすべてPOSTに統一する?
ここまでの1〜3のケースを比較していく中で、実務の設計例としてPOSTを使用するケースが多いことにお気づきいただけたかと思います。
実務において可読性やバグ防止の観点を重視した結果、RESTの原則としては例外的な扱いであるはずの「動詞はじまりのエンドポイント」が多く見られるケースも少なくありません。(RESTの原則から逸脱したPOSTの利用=操作に注目した命名、となることが多い)
ここで「統一性」という観点で設計を考えた時に選択肢として浮上するのが「変更系のリクエストをすべてPOSTに統一して扱う」というものではないでしょうか。
この方針のメリットとデメリットを考えてみましょう。
メリット
- クライアント側の実装がシンプルになる(「POSTによるリクエスト」のみとなる)
- API設計が動詞ベースになることで、処理内容が理解しやすくなり可読性が上がる場合がある
- 例)ECサイトにおける注文キャンセル処理など(
POST /potatoes/{id}/cancel)
- 例)ECサイトにおける注文キャンセル処理など(
デメリット
- RESTの原則から大きく逸脱する
- 原則に沿うことで得られるキャッシュや冪等性などのメリットを失う可能性がある
- API設計が動詞ベースになりがち (
POST /potatoes/{id}/activateなど)
このように、RESTの原則を尊重しつつも、実務では可読性や統一性を優先するケースもあります。
また、GETとPOSTを使用する方針とするなど、どこまでRESTの原則から逸脱するかついても、考える余地があるかと思います。
いずれにせよ、プロジェクトとして一貫できることが肝となるのではないでしょうか。
おわりに
いかがでしたでしょうか。
RESTの原則と実務で起こりうる議論を比較し、プロジェクトとして何を重要視していくのかによって、設計方針として取るべき選択肢が様々あることが確認できました。
それぞれにメリットとデメリットがあり、何を選択するにしろ、実務ではその方針の一貫性を保つことが大切かなと思います。
- 情報共有ができず、後の人がなぜこの選択をしたのかがわからない
- 判断基準が明確化されておらず、人によって方針がバラバラとなり、結果的にカオスのような状態になってしまった
ということにもなりかねません。
特に、RESTの原則から逸脱する方針としたものについては、Open APIスキーマなどを活用して「なぜこの設計を選択したのか」「選択する基準は何か」をプロジェクト全体で共有できる形にできれば、キャッチアップもスムーズになるのではないでしょうか。
次回は記事本文でも少し触れたように、第三の選択肢としてGraphQLを利用する方法について、まとめていければと考えております。
以上です。最後まで閲覧いただきありがとうございます。
参考
REST原則について
HTTPリクエストメソッドおよびAPIリファレンス
- https://developer.mozilla.org/ja/docs/Web/HTTP/Reference/Methods
- https://nec-baas.github.io/baas-manual/latest/developer/ja/rest-ref/user/login.html
- https://wagby.com/wdn8/restapi-logon.html#logon
- https://documentation.commvault.com/2022e/essential/rest_api_post_deactivate.html
その他
Discussion