はじめに
普段はソフトウェアエンジニアとして働いています。最近Web APIの設計をまるっと考える機会があったので、改めてWeb APIの設計についてまとめてみることにしました。今回扱うAPIは、HTTPプロトコルを使用したRESTful APIです。
そもそもRESTful APIって何だっけ、というところから、API設計の際に意識すべきポイントまで書いてみました。
対象読者
Web APIの設計をやったことが無い方、これから設計する必要がある方
RESTful APIとは
RESTとは、Webサービスの設計思想の一つです。そして、RESTの原則に従って設計されたAPIがRESTful APIです。この原則に従って設計することで、シンプルで使いやすいAPIを作ることができます。
超ざっくりとした一般的な説明だけ載せておきます。↓
RESTの原則
クライントとサーバーの分離
クライアントとサーバーは完全に分離している必要があります。
これにより、クライアントとサーバーは互いを意識せずに開発可能となります。
ステートレス
セッション管理を完全にクライアントで行います。これにより、リクエストは他のリクエストから完全に独立して処理されます。
キャッシュ可能性
サーバーの応答時間を改善するために、クライアントはクライアントまたは仲介者にレスポンスを保存(キャッシュ)しそれを利用することができます。
統一インターフェース
統一された規格で情報をやり取りする必要があります。今回であればHTTPというプロトコルでやりとりします。
アドレス可能性
すべてのリソースは一意の識別子(URI)で表現される必要があります。
例えば、qiitaのブログの記事は、以下のようにユーザーidと記事のidによって記事(リソース)が一意に特定されます。
https://qiita.com/{ユーザーid}/items/{記事のid}
オンデマンドのコード
サーバーはソフトウェアプログラミングコードをクライアントに転送することにより、クライアント側の機能を一時的に拡張またはカスタマイズできます。
API設計の流れ
本題に入っていきます。
APIを設計する際、まずはどのリソースに対してどのようなリクエストを送り、その結果どのようなレスポンスが返ってきてほしいのかを考えます。
リクエスト
HTTPリクエストは、以下の3つで構成されています。
---------- リクエストライン ------------
POST /items HTTP/1.1
---------- リクエストヘッダー ----------
Accept: image/gif, image/jpeg, */*
Accept-Language: ja
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (Compatible; MSIE 6.0; Windows NT 5.1;)
Host: www.xxx.zzz
Connection: Keep-Alive
---------- リクエストボディ ------------
{
name: 'hogehoge',
detail: 'piyopiyo',
}
- リクエストライン
- HTTPメソッド
- URI
- HTTPバージョン
- リクエストヘッダー
- リクエストについての情報や属性(メタデータ)
- リクエストボディ
- 処理に必要な情報
この中で、今回はHTTPメソッド、URI、リクエストボディをどのように設計すべきかを説明していきます。
HTTPメソッドとURIはリクエストラインに含まれます。
例えば以下のようなものです。商品(item)情報を取得するAPIを想定してみました。
GET /items/1 HTTP/1.1
これは、items(商品)のid=1番をGETしてね、というリクエストになります。(というか、そのように設計します)
ここでGETの部分はHTTPメソッド、/items/1の部分はURIと呼ばれます。
HTTPメソッドのURIの組み合わせで、どのリソースに対して何をするのかを表します。
HTTPメソッド
代表的なHTTPメソッドは以下の4種類です。操作の内容に応じて適切なメソッドを選びます。
メソッド名 | |
---|---|
GET | リソースを取得する際に用います |
POST | リソースを追加する際に用います |
PUT | 既存のリソースを更新する際に用います |
DELETE | リソースを削除する際に用います |
URI設計
URIは、どのリソースに対して操作を行うのかが一目でわかるように設計します。
↓id=123の商品リソースのURIの例
https://example.com/items/123
↓id=456の商品についているid=11のレビューのリソースのURIの例
https://example.com/items/456/reviews/11
【悪い例】
-
新規追加や更新で使うURIであっても、URIに動詞を含めるのは極力避けるべきです
-
動作はHTTPメソッドで表現しましょう
-
NG
https://example.com/add-items
https://example.com/update-items/123
-
OK
-
POST https://example.com/items
=> 追加 -
PUT https://example.com/123
=> 更新
-
-
-
リソースは複数形で書くべきです
- NG
https://example.com/item
- NG
-
冗長なURLは避けましょう
- NG
https://example.com/items/list/all
- NG
-
一部の人しか理解できない略語や人間が意味を理解できない文字列などは使用すべきではありません
-
サーバー側のアーキテクチャを意識した文字列は使用しないようにしましょう
- NG
https://example.com/postgresql/items/123
- NG
-
名詞はハイフンでつなぐようにしましょう
- NG
https://example.com/online_games
-
- NG
https://example.com/online-games
- NG
- NG
-
大文字小文字が混在しないいようにしましょう
- NG
https://example.com/Items
- OK
https://example.com/items
- NG
リクエストと一緒に送るデータの検討
リクエストを送る際は、一緒に情報を送りたい場合が多いです。例えば、参照したいリソースのid、検索条件に使う文字列、新規にリソースを作成する場合の内容など。
リクエストにデータを載せる方法は主に以下の3種類です。
- パスパラメータ
- クエリパラメータ
- リクエストボディ
パスパラメータ
パスパラメータは、例えば以下のような形です。
https://example.com/items/12345
この場合、12345の部分がパスパラメータに当たります。
リソースを一意に特定するのに必要な情報は、このようにパスの中に含めて送ります。
リソースが階層構造を持っている場合は、階層構造がわかるようなURIにします。
例) id
https://example.com/items/12345/reviews/678
クエリパラメータ
クエリパラメータは、例えば以下のような形です。
https://example.com/items?name=apple
上記の例だと、name=appleという情報が送られます。
一例として、検索条件などを送る時に使います。クエリパラメータは省略可能です。
【パスパラメータとクエリパラメータの使い分け】
パスパラメータはリソースを一意に特定するために必要な情報、クエリパラメータは省略可能な情報、一意に特定するためではない情報などを送る場合に使うのが一般的です。
以下のように、リソースを一意に特定するのに必要な省略できない情報はクエリパラメータに含めるべきではありません。
- NG
https://example.com/items?id=123
- OK
https://example.com/items/123
リクエストボディ
POST, PUTなどで追加、更新するデータ等はリクエストボディで送ります。なお、HTTPメソッドがGETの場合はリクエストボディでデータを送ることはできません。パスパラメータ、クエリパラメータを使用しましょう。
{
name: 'ラジオ',
detail: '防水で軽量なラジオです',
price: '2000',
}
【悪い例】
-
リソースを一意に識別するための情報はリクエストボディではなくパスパラメータで送りましょう
- URIはリソースを一意に特定できるようにすべきです
NG↓
PUT /items
-----
{
id: 123
name: '高性能ラジオ',
detail: '完全防水で軽量なラジオです',
price: '3000',
}
OK↓
PUT /items/123
-----
{
name: '高性能ラジオ',
detail: '完全防水で軽量なラジオです',
price: '3000',
}
レスポンス
リクエストに対して、どのようなレスポンスを返すかを設計します。
レスポンスの設計で意識すべきことはいくつかありますが、今回はその中でも特に重要な以下二点に絞って書きます。
- レスポンスヘッダーのステータスコード
- レスポンスボディ
ステータスコード
HTTPレスポンスでは、ステータスコードでリクエストの処理結果を表します。404 Not Found などが有名かと思います。
レスポンスは大きく以下の5種類に分類されます。
- 情報レスポンス (100 – 199)
- 成功レスポンス (200 – 299)
- リダイレクトメッセージ (300 – 399)
- クライアントエラーレスポンス (400 – 499)
- サーバーエラーレスポンス (500 – 599)
代表的なステータスコードを列挙してみます。自分が開発している時に頻繁に見るものを中心に並べました。
ステータスコード | 説明 |
---|---|
200 OK | リクエストが成功したことを示します。 |
400 Bad Request | クライアント側のエラーで、不正なリクエストが送られたことにより処理ができない場合を示します。 |
401 Unauthorized | 認証されていないことを示します。 |
403 Forbidden | アクセス権がないことを示します。 |
404 Not Found | リクエストされたリソースを発見できないことを示します。 |
429 Too Many Requests | 一定時間内に大量のリクエストが送信されたことを示します。 |
500 Internal Server Error | サーバー側で処理できなかったことを示します。 |
503 Service Unavailable | サーバー側がダウンしているなどの原因でリクエストを受け取る準備ができていないことを示します。 |
処理結果に応じて適切なステータスコードを返してあげることが重要です。
ステータスコードはほかにもたくさんあります。詳しくはこちら↓
https://developer.mozilla.org/ja/docs/Web/HTTP/Status
レスポンスボディ
今回は一般的なケースとして、JSON形式のレスポンスを考えます。
例えば、GET items/123
に対するレスポンスを考えます。id=123の商品情報を取得したいとします。
各商品は、id、商品名、商品説明、レビューのリストを持っているとします。その場合、レスポンスは以下のようになります。
{
"id": 123,
"name": "ワイヤレススピーカー",
"detail": "高音質で完全防水のスピーカーです。"
"reviews": [{
"id": 1,
"userId": "001",
"comment": "買って良かったです。"
},{
"id": 2,
"userId": "011",
"comment": "すぐに壊れました。"
}]
}
【悪い例】
-
クライアントが必要な情報を得るために何度もリクエストを送る必要がある
- クライアントが必要なデータを極力少ない回数のリクエストで得られるように設計しましょう
-
複雑な階層構造になっている
- できるだけシンプルなレスポンスになるように意識しましょう
- NG↓ (この場合、info の階層は無い方がシンプルでしょう)
{ id: 123, info: { name: "ワイヤレススピーカー", detail: "高音質で完全防水のスピーカーです。" reviews: [ { "id": 1, "userId": "001", "comment": "買って良かったです。" }, { "id": 2, "userId": "011", "comment": "すぐに壊れました。" } ] } }
-
わかりにくい、長い、統一されていない命名
- 名称は簡潔、だれが見ても理解できる、一貫性のある命名を心掛けましょう
エラー時のレスポンス設計
エラーが発生した時、レスポンスボディにどのような情報を載せればよいか検討します。
ステータスコードによって大まかなエラーの原因を示すことはできますが、それだけだとクライアント側でエラーに対処するには情報が不十分な場合が多いです。なので、基本的にエラーが発生した場合は、適切なステータスコードと共にエラーの詳細をレスポンスボディに載せるのが良いと思います。
独自のエラーコードを定義してレスポンスに含めると、クライアント側でのエラーハンドリングが容易になります。
{
errorCode: 'item_not_found'
errorMessage: 'Cannot find the item.'
}
終わりに
以上が、Web APIを設計する際に意識すべきことの基本になります。この記事が今後API設計をする際の参考になればうれしいです。
最後までお読みいただきありがとうございました。