WebAPI
api
読書
書籍

新システムWebAPI設計のために Web API: The Good Parts を読んだから読んだ結果をまとめてみる


経緯

新しく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://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": {
... 実際のデータ ...
}
}

上記のようなheaderresponseというエンベローブを使う方式は非推奨。せっかく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をこれから設計する人、もしくは設計した経験がある人も振り返りとして読んでみると参考になる書籍だと思います。