はじめに
こんにちは @yuto_1220 です。今回はBackend Engineerに向けてREST APIにおけるURI設計について記述しようと思います。自身が最近経験したプロジェクトにおいて、それまでよく知らなかったREST APIのURIの設計について学んだので備忘録も兼ねて記述しようと思いました。
HTTPリクエストメソッドについて
REST APIはHTTP通信によって実現されるAPIですが、このHTTP通信は、いくつかのメソッドを持っており、それらのメソッドの指定によって、そのAPIがどのような操作を行いたいのかが一目で分かるようになっています。例えば、リソースの取得を行いたいのであればGET
を用います。
GET
リソースの一覧を取得したり、特定のリソースの取得などを行います。
POST
データの送信を行います。それによって、新しいリソースの作成を行ったりします。
DELETE
指定したリソースを削除します。
PATCH
リソースを部分的に変更します。
PUT
特定のリソースを送られたデータで置換します。つまり、リソースの作成 or 更新となります。
PATCH
とPUT
の違いについて
PATCH
とPUT
は一言で言うとリソースの更新を行うメソッドであると一般的に言われます。そのこと自体は間違っていませんが、それぞれのメソッドは更新対象が少し違います。
例として以下のようなitemId: 1
のリソースがあるとします。(リソースという概念は曖昧であり色々あるかと思いますが、今回は例としてjsonとして記述しています。)
"pen": {
"length": 10,
"color": "black",
"owner": "taro"
}
このリソースのcolorをredに変更してみます。
PATCH
で適切にリクエストを送る場合は以下のようになります。
$ curl -X PATCH -H "Content-Type: application/json" \
-d '{"pen": {"color":"red"}}' \
https://dummy-host.com/items/1
では、次はPUT
で適切なリクエストを送ってみましょう
$ curl -X PUT -H "Content-Type: application/json" \
-d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
https://dummy-host.com/items/1
これでitemId: 1
のリソースは意図した通りにcolorがredになりました。
"pen": {
"length": 10,
"color": "red",
"owner": "taro"
}
では、今度はこれを逆にしてみましょう。
本来はPATCH
で送るべきリクエストをPUT
で送るとリクエストとその結果は以下のようになります。
$ curl -X PUT -H "Content-Type: application/json" \
-d '{"pen": {"color":"red"}}' \
https://dummy-host.com/items/1
"pen": {
"color": "red"
}
length
とowner
が無くなってしまいました。PUTは置換なのでこうなりますね。これは意図した結果ではありません。
次に、本来はPUT
で送るべきリクエストをPATCH
で送ってみます。そのリクエストと結果は以下のようになります。
$ curl -X PATCH -H "Content-Type: application/json" \
-d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
https://dummy-host.com/items/1
"pen": {
"length": 10,
"color": "red",
"owner": "taro"
}
上手く行ったように見えますね。PATCH
はリソースの部分的な変更ですが、リソース全体の変更としても上手くいく場合もあると思います。ですが、サーバーによってはPUT
とPATCH
の違いを認識しそれぞれのメソッドで適切に処理することがあります。なので、適切にHTTPメソッドを指定する必要がありますし、APIの使用者に上手く意図が伝わらないこともあります。
URI設計の基本
{リソース名}s/{リソースid}
REST APIでは特定のリソースを指定する時は{リソース名}s/{リソースid}
の形になります。DELETE
メソッドでitemId: 1
のリソースを削除したい場合は以下のようになります。
$ curl -X DELETE https://dummy-host.com/items/1
また、itemId: 1
の中のpartId: 2
のpartを削除したい場合は、{リソース名}s/{リソースid}
に従うと以下のようになります。
$ curl -X DELETE https://dummy-host.com/items/1/parts/2
この場合はitemとpartは親子関係を持っていることが必須になります。上記の例ではitemとpartの関係は1:Nを想定しています。
また、itemとpartの関係が1:1の場合であれば、以下のようなURIでもpartを特定して削除することができます。
$ curl -X DELETE https://dummy-host.com/items/1/part
注意点としてはモデル間に適切な1:Nや1:1関係がないとこれらのようなURIにはなりません。
例えば、itemとsportの関係には特に何も関係が無いと仮定します。その場合は以下のようなURIは成り立たないということですね。
$ curl -X DELETE https://dummy-host.com/items/1/sports/2
itemId: 1
の中にsportsが無いのであればそもそも不自然なURIですね。(本当にこのモデル関係が存在するのであればもちろん成り立ちますが。)
各場面でのURI設計例
リソースの一覧の取得
このエントリポイントでは/{リソース名}s
になります。つまり、itemのリソースの一覧を取得したい場合は以下のようになります。
$ curl -X GET https://dummy-host.com/items
特定のリソースの取得
このエントリポイントでは、{リソース名}s/{リソースid}
を用いることになります。つまりitemId: 1
のリソースを取得する場合は以下のようになります。
$ curl -X GET https://dummy-host.com/items/1
特定のリソースの検索
このエントリポイントではクエリパラメータを用います。また、基本的な概念としては検索 = 条件で絞り込んだ一覧です。例として、colorがblackのitemを検索してみましょう。
$ curl -X GET https://dummy-host.com/items?color=black
これはよく見かける形式だと思います。もちろん、クエリパラメータは条件による絞り込みのためだけに存在しているわけではありませんが、検索のエントリポイントを設計する際にはこのように用いるのがベターです。
リソースの新規作成
リソースを新規作成、つまりItemモデルの新しいリソースを作成するエントリポイントは、基本的には/{リソース名}s
のようになります。itemの新規リソースを作成したい場合は以下のようになります。リソースの新規作成なのでメソッドはPOST
ですね。
$ curl -X POST -H "Content-Type: application/json" \
-d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
https://dummy-host.com/items
リソースの新規作成のバリエーション
itemを新規作成したい場合はPOST /items
のURIを作成してあげれば良いことは分かりました。
ですが、例えば「既存のitemから複製して作成したい場合」や「色や持ち主を受け取ってitemを新規作成したい場合」があると仮定します。
これらのitemを作成するロジックはそれぞれ多少異なっていることが多いのでは無いかと思います。なので、ロジックは「複製での作成」なのか「受け取った入力からの作成」なのかは重大な関心事です。これを判別するには、一例としてリクエストボディで受け取ると良いかと思います。
複製の場合は
$ curl -X POST -H "Content-Type: application/json" \
-d '{"itemId":1}' \
https://dummy-host.com/items
itemId
だけを受け取れば、ロジックはそれを既存のitem(この場合はitemId: 1
)の複製だと見なします。
入力を受け取った場合は
$ curl -X POST -H "Content-Type: application/json" \
-d '{"color":"black", "owner": "taro"}' \
https://dummy-host.com/items
itemId
ではなくitemリソースを構成する要素を受け取った場合は入力からの作成だとロジックは見なします。
何が言いたいのかというとAPIを提供するサービス内でitemを作成するエントリポイントはここだけにすべきだと言うことです。
APIを提供するサービスがRuby on Railsで実装されている場合は、itemを作成するエントリポイントはPOST /items
でコントローラーとしては/app/controllers/api/v1/items_controller.rb
内のcreate
メソッドで実装されると思います。これは多くの場合においては正しいと思います。
ただ、油断をすると「itemを新規作成する」エントリポイントがアプリケーション中に散見されることがあります。
例えば、既存のitemの複製から作成する場合はPOST /duplications
で/app/controllers/api/v1/duplications_controller.rb
のcreate
メソッドに記述されたり、すでにサービス内部にある作成者の情報を使用して作成する場合にPOST /maker-items
のエントリポイントでapp/controllers/api/v1/maker_items_controller.rb
のcreate
メソッドを実装してしまうことがあります。
そのエントリポイントが最終的にitemを新規作成したいのであれば、基本的には全てPOST /items
で処理すべきです。同じ入り口を用意し同じロジックを用いなければ不要な重複ロジックを生んだり、他の開発者が意図を理解できないことがあります。APIの処理はどこからやってくるのかではなく最終的には何をしたいのかで考えるとURIやロジックの置き場所が正しくなりやすいです。
リソースの更新
上述したように更新にはいくつかの方法があります。1つのリソースを特定して部分的に更新する場合を考えてみます。この場合は以下のように{リソース名}s/{リソースid}
で特定のリソースを指定しPATCH
でリクエストを送るようにするのがベターだと思います。
$ curl -X PATCH -H "Content-Type: application/json" \
-d '{"pen": {"color":"red"}}' \
https://dummy-host.com/items/1
リソースを置換して更新したい場合は以下のようになります。
$ curl -X PUT -H "Content-Type: application/json" \
-d '{"pen": {"length": 10, "color": "red", "owner": "taro"}' \
https://dummy-host.com/items/1
削除
{リソース名}s/{リソースid}
のようにリソースを特定し、DELETE
メソッドでリクエストを送ります。
$ curl -X DELETE https://dummy-host.com/items/1
ちなみに、itemId: 1
の中のpartId: 2
のリソースを削除したいのであれば以下のようになると思います。
$ curl -X DELETE https://dummy-host.com/items/1/parts/2
一括でのリソースの更新 & 削除
これにはいくつかの方法があるかと思いますが、こちらを参考にされると良いかと思います。こちらの記事にあるような方法を取るか、もしくは適切にURIやロジックを設定できるのであれば他の方法もあったりするのかなと、私もこのことについては最終的な結論を出せていません。
上記以外の操作について
上記までは一般的なCRUD操作のエントリポイントについて記述してきましたが、これに当てはまらないエントリポイントも多いのではないかと思います。例えばitemId: 1
のものをpdfファイルとして取得したい場合は、以下のようになるかと思います。
$ curl -X GET https://dummy-host.com/items/1/pdf
この辺りはフロントエンド側との意思疎通が取れてたりすれば、ある程度自由にできると思います。参考となるのはGoogleのAPI設計ガイドです。
HTTPメソッド指定での例外
以上でHTTPメソッドのルールを説明してきましたが、例外もあります。例えば以下のようにPOST
でリソースを取得するような場合です。
- 大量のデータ送信:
GET
リクエストはURLの長さに制限があるため、大量のデータを送信する必要がある場合にはPOST
が適しています。 - 機密情報の送信:
GET
リクエストのパラメータはURLに含まれるため、ログやブラウザの履歴に残りやすいです。機密情報を扱う場合は、POST
メソッドを使って情報をリクエストボディに含めることで、より安全にデータを送信できます。
このような例外もありますが、基本的にはルールに則った形でメソッドを定義しましょう。
まとめ
今回はREST APIのURI設計について記述しました。また、参考にはGoogleのAPI設計ガイドのリンクを貼っているので、そちらもぜひ参照してみてください。最後に自身が最近見た中でも原則に則った良いURI設計がなされているのはクラウドサイン WebAPIです。これはURIやHTTPリクエストメソッドを見れば何をどうする操作なのかが一目瞭然であり、分かりやすく使いやすいURIの設計だと思います。理解しやすいURIは開発やデバッグを容易にし、不要なコミュケーションコストを削減してくれます。
最後に
人それぞれ思想はあると思いますので、絶対的に正しいわけではありません。ただ、基本的にはこんな感じになるかとは思います。
参考
Google API 設計ガイド: https://cloud.google.com/apis/design?hl=ja
MDN HTTP リクエストメソッド: https://developer.mozilla.org/ja/docs/Web/HTTP/Methods