経緯
新しくWebAPIを設計するにあたって、何の道標もなく設計していくのは危険だと思ったので、「Web API: The Good Parts」を読んで設計をしています。
読んでなるほどと思ったことをここに書き出していきます。書き出した内容は新API設計の参考にしますし、今後の私のWebAPI設計の参考にもなると思います。
なんでこの書籍選んだの?
- ネットでの評判上々
- とりあえずオライリー選んどきゃ間違いないっしょ!
エンドポイントの設計とリクエストの形式
WebAPIにおけるエンドポイントとはURIのことです。
覚えやすく、どんな機能を持つURI なのかがひと目でわかるようにする
短く入力しやすいURI
× http://api.example.com/service/api/search
○ http://api.example.com/search
人間が読んで理解できるURI
× http://api.example.com/sv/u/
○ http://api.example.com/service/user/
× http://api.example.com/seihin/12345
○ http://api.example.com/products/12345
大文字小文字が混在していないURI
× http://api.example.com/Users/12345
○ http://api.example.com/users/12345
サーバ側のアーキテクチャが反映されていないURI
× http://api.example.com/cgi-bin/get_user.php?user=100
○ http://api.example.com/users/100
ルールが統一されたURI
× Bad
処理 | URL |
---|---|
友達の情報の取得 | http://api.example.com/friends?id=100 |
メッセージの投稿 | http://api.example.com/friend/100/message |
○ Good!
処理 | URL |
---|---|
友達の情報の取得 | http://api.example.com/friends/100 |
メッセージの投稿 | http://api.example.com/friends/100/messages |
HTTPメソッド
WebアプリケーションではGETとPOSTを使う通信が一般的だが、WebAPIではそれ以外のメソッドを利用するケースが多くなっている。
メソッド | 説明 |
---|---|
GET | リソースの取得 |
POST | リソースの新規登録 |
PUT | 既存リソースの更新 |
DELETE | リソースの削除 |
PATCH | リソースの一部変更 |
HEAD | リソースのメタ情報の取得 |
example
目的 | メソッド | URL |
---|---|---|
ユーザー一覧取得 | GET | http://api.example.com/v1/users |
ユーザー新規登録 | POST | http://api.example.com/v1/users |
特定ユーザー取得 | GET | http://api.example.com/v1/users/:id |
ユーザー情報更新 | PUT/PATCH | http://api.example.com/v1/users/:id |
ユーザー情報削除 | DELETE | http://api.example.com/v1/users/:id |
その他注意点
複数形の名詞を利用する
× http://api.example.com/user/12345
○ http://api.example.com/users/12345
世界的に複数形のほうが適切であるとされているらしい。
でも単数形が使われている有名サービスもたくさんあるし、必須ではない。
利用する単語に気をつける
英語がネイティブではない場合、残念ながら適切な単語を選ぶのはなかなか難しい。
searchとfindとか。写真はpicture ではなくphoto のほうが使われています。
迷ったら様々なAPIを紹介しているこのサイト([ProgrammableWeb](http://www.
programmableweb.com/))を参考にするといいらしい。
スペースやエンコードを必要とする文字を使わない
× http://api.example.com/v1/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC/123
↑これ、何のAPIか一目でわかりますか?
単語をつなげる必要がある場合はハイフンを利用する
× http://api.example.com/v1/users/12345/profile_image
× http://api.example.com/v1/users/12345/profileImage
○ http://api.example.com/v1/users/12345/profile-image
必須ではない。ある程度好みでOK。
検索APIで追加表示するときの設計
相対位置でデータを取ってこないで絶対位置でデータを取得する
× 相対位置 先頭から数えて何番目から何番目のデータをください
○ 絶対位置 このIDより新しいデータを20件ください
何故なら絶対位置のほうがパフォーマンスに優れているから。そして、途中でデータが挿入されても正しいデータを取得できるから。
ログイン周りのAPI設計
OAuth2.0を使うべき。
一般に公開するAPIなのか、内部利用するAPIなのか
一般に公開するAPI
- 出来るだけ多くの利用者が利用しやすいような汎用的な設計を行う
内部利用するAPI
- 必ずしも汎用的に作る必要はない
- 「1 スクリーン1API コール、1 セーブ1API コール」
- すなわち、ひとつの画面を表示するためにコールするのが1 つのAPIで済むように、それに合わせたAPI を用意し、何らかのデータをサーバに保存する場合にも1 回のコールで済むように、それ向けのAPI を用意するのがよい
レスポンスデータの設計
データフォーマット
json必須
とりあえずjsonに対応しとけばOK。必要であればxmlやjsonpに対応する。
jsonp対応する場合はエラー処理が複雑っぽい。
レスポンスの内容をユーザーが選べるようにする
返却データが大きい場合は、レスポンス内容をユーザーに選択させる設計にするのが有効
予めレスポンスグループとして「small」「medium」「large」の3区分位作っといてユーザーに選択させるのも有効
例 http://api.exmample.com/v1/users/12345?fields=name,age
例 http://api.exmample.com/v1/users/12345?fields=medium
エンベローブ(封筒)は必要か
{
"header": {
"status": "success",
"errorCode": 0,
},
"response": {
... 実際のデータ ...
}
}
上記のようなheader
とresponse
というエンベローブを使う方式は非推奨。せっかくHTTPを使ってるんだから、処理に成功したか失敗したかはHTTPステータスコードで表現すべきである(後述)
データをフラットにするべきか
正直好み。しかし、こういう意味のない構造化はフラットにすべきである(DBの構造がそうなっているから、とか内部都合に左右されるべきでない)
{
"id": 23245,
"name": "Taro Yamada"
"profile": {
"birthday": 3456,
"gender": "male",
"languages": [ "ja", "en"]
}
}
↓
{
"id": 23245,
"name": "Taro Yamada"
"birthday": 3456,
"gender": "male",
"languages": [ "ja", "en"]
}
配列で返すか、オブジェクトで返すか
配列で返すとjsonハイジャックというセキュリティ上のリスクがあるので、オブジェクトで返そう
配列でそのまま返す例
[
{
"id": 234342,
"name": "Taro Tanaka",
"profileIcon": "http://image.example.com/profile/234342.png"
},
{
"id": 93734,
"name": "Hanako Yamada",
"profileIcon": "http://image.example.com/profile/93734.png"
}
]
オブジェクトで包む例
{
"friends": [
{
"id": 234342,
"name": "Taro Tanaka",
"profileIcon": "http://image.example.com/profile/234342.png"
},
{
"id": 93734,
"name": "Hanako Yamada",
"profileIcon": "http://image.example.com/profile/93734.png"
}
]
}
検索結果の続きがあるかをどう返すか
続きがあるよフラグを設けるとよい。例えば何かを10件ずつ表示する機能の場合は、APIでは11件取得して11件目があれば「続きがあるよフラグ」をtrueにして返すのが良い
{
"friends": [
{
"id": 234342,
"name": "Taro Tanaka",
"profileIcon": "http://image.example.com/profile/234342.png"
},
{
"id": 93734,
"name": "Hanako Yamada",
"profileIcon": "http://image.example.com/profile/93734.png"
}
],
hasNext: true
}
日付のフォーマット
RFC 3339を使おう。
例 2015-10-12T11:30:22+09:00
大きな整数の取り扱い
桁数の大きな数値はjavascriptで誤動作するので文字列で返すのが良い。
var data = JSON.parse( '{"id":462781738297483264\}' );
console.log(data.id); // 462781738297483260
レスポンスデータを設計するべきうえで漠然と考慮すべきこと
APIは内部で持っているDB のテーブル構造をそのまま反映する必要は無い。API のユースケースをよく考え、ユーザーが最もシンプルに扱うことができる設計を目指す。
エラーの表現
HTTPステータスコードでエラーを表現する
後述します。
詳細なエラー内容をレスポンスに含める
こんな感じで、以下はTwitterの例
{
"errors":[
{
"message":"Bad Authentication data",
"code":215
}
]
}
HTTPの仕様
HTTPステータスコードにはちゃんと意味がある
ステータスコード | 意味 |
---|---|
100番台 | 情報 |
200番台 | 成功 |
300番台 | リダイレクト |
400番台 | クライアントサイドに起因するエラー |
500番台 | サーバーサイドに起因するエラー |
もっと詳しく(太字のは覚えてね)
ステータスコード | 名前 | 意味 |
---|---|---|
200 | OK | リクエストは成功した。 |
201 | Created | リクエストが成功し、新しいリソースが作られた |
202 | Accepted | リクエストは成功した |
204 | No Content | コンテンツなし |
300 | Multiple Choices | 複数のリソースが存在する |
301 | Moved Permanently | リソースは恒久的に移動した |
302 | Found | リクエストしたリソースは一時的に移動している |
303 | See Other | 他を参照 |
304 | Not Modified | 前回から更新されていない |
307 | Temporary Redirect | リクエストしたリソースは一時的に移動している |
400 | Bad Request | リクエストが正しくない |
401 | Unauthorized | 認証が必要 |
403 | Forbidden | アクセスが禁止されている |
404 | Not Found | 指定したリソースが見つからない |
405 | Method Not Allowed | 指定されたメソッドは使うことができない |
406 | Not Acceptable Accept | 関連のヘッダに受理できない内容が含まれている |
408 | Request Timeout | リクエストが時間以内に完了しなかった |
409 | Conflict | リソースが矛盾した |
410 | Gone | 指定したリソースは消滅した |
413 | Request Entity Too Large | リクエストボディが大きすぎる |
414 | Request-URI Too Long | リクエストされたURI が長すぎる |
415 | Unsupported Media Type | サポートしていないメディアタイプが指定された |
429 | Too Many Requests | リクエスト回数が多すぎる |
500 | Internal Server Error | サーバ側でエラーが発生した |
503 | Service Unavailable | サーバが一時的に停止している |
HTTPステータスコードを理解して正しく使ってレスポンスする
- API のリクエストが成功した、つまりクライアントが意図した動作をサーバ側が完了できた場合は200 番台を返す。
- リクエストに何らかの不備があってサーバ側で意図を理解できなかったり、リクエストは理解できたけれど実行できない場合などは400 番台を返す
- サーバ側がエラーを起こした場合は通常のウェブアプリケーションと同様に500 を、メンテナンスや何らかの理由でサービスを停止している場合は503を返す。
200番台: 成功
- 処理に成功したら200を返す。
- 正しくはPOSTの場合は201を、DELETEメソッドの場合は204を返すのがベスト。
- POSTメソッドでデータの作成に成功したら、作成されたデータをレスポンスする。
- PUTメソッドでデータの更新に成功したら、更新されたデータをレスポンスする。
- DELETEメソッドでデータの削除に成功したら、削除されたデータをレスポンスする。
400番台: クライアント側の問題
- 「あなたが誰だかわからないよ」は401を返す。
- 「あなたが誰だかはわかったけど、この操作はあなたには許可されてないよ」は403を返す。
- 「そんなページないよ」は404を返す。
- 他の400番台のエラーで表すことが出来ないエラーは400を返す。(入力パラメータ誤り等)
500番台: サーバー側の問題
- サーバー側の処理で何か予期せぬことが起きたら500を返す。
- 意図的にせよ、意図的でないにせよ、サーバーが応答しなくなったら503を返す。
独自のHTTPヘッダを定義する
HTTPヘッダを新しく定義する場合は頭に"X-"と接頭辞を付けて、次にサービスやアプリケーション、組織の名前を付けるのが一般的
例 X-AppliName-Session-Id
設計変更しやすいWebAPIを作る
APIをバージョンで管理する
一度公開したAPIは出来るだけ変更しない。もし機能追加や何らかの大きな変更が必要な場合は、新しいAPIを作るべきである。古い形式でアクセスしてきているクライアントに対しては、それまでと変わらないデータを返し、新しい形式でのアクセスには新しい形式のデータを返す。すなわち複数バージョンのAPIを提供する。
例
http://api.example.com/v1/users/123
http://api.example.com/v2/users/123
http://api.example.com/users/123?v=1
http://api.example.com/users/123?v=2
バージョンをURLで受け取るか、クエリ文字列で受け取るか
決まりはない。好み。有名サービスは前者が多数。
後者のデメリットはクエリ文字列が省略された場合、どのバージョンのAPIが動くのか一目でわからない。また、省略された場合に最新のAPIとして動くのか、古いAPIとして動くのか、エラーにするのか処理を入れるのも手間
バージョンを変える際の指針
- 後方互換性を保つことが可能な変更は可能なかぎり同じバージョンで何とかする。
- どうしても後方互換性を保ったまま修正を行うことが難しい変更を加えなければならないときにのみ、バージョンを上げるべき。
古いAPIバージョンの提供を終了する
広く一般に公開されたAPI の提供を終了する場合には、事前に終了日時をアナウンスして、それまでに対応してくれるように周知徹底しなければならない。
堅牢なWebAPIを作る
サーバーとクライアント間の情報の不正入手
こんなことに対応しておくこと
- HTTPS化
悪意のあるアクセスへの対策
こんなことに対応しておくこと
- XSS対策
- XSRF対策
- jsonハイジャック
- リクエストパラメータの改ざん
- リクエストの再送信
大量アクセスへの対策
こんなことに対応しておくこと
- ユーザーごとのアクセスを制限する
まとめ
- 「設計の美しいWebAPIは使いやすく、変更しやすく、頑強であり、恥ずかしくない」そう本書の中にありましたが、まさにその通りだと思います。私は実際、過去に古いWebAPIを改修した経験がありますが、そのAPIは保守しにくく修正しにくく、使いにくいと感じていました。どうすればそういった使いにくさだったり保守しにくさを改善できるか考えていましたが明確な答えは導き出せずにいました。この書籍を読むことである程度は答えが出せたと思います。今後のWebAPI設計の糧になったと感じます。
- ただし誤字脱字が多かったです。数えてないけど自分が見つけただけで10か所は軽くあったように思います。一度読めば分かる誤字脱字がほとんどだったので残念でした。
- WebAPIをこれから設計する人、もしくは設計した経験がある人も振り返りとして読んでみると参考になる書籍だと思います。