api
読書
設計

Web API: The Good Partsを読んだまとめ

このドキュメントについて

Web API: The Good Partsを読んだ内容を友人とやっている勉強会で発表する際に使う資料としてここにまとめた


この本について

  • WebAPIを設計する際のベストプラクティスみたいなのを解説した本
  • リソース指向とかを事前に知っておくとすんなり入ってくる内容な気がする
  • そんなに厚い本ではなくサラッと読めるのでおすすめ

エンドポイントとリクエストメソッドの設計


エンドポイントの設計

筆者が重要だと考える下記にそって設計を行うと良い。

  1. 短く入力しやすい
  2. 人間が読んで理解できる
  3. 大文字小文字が混在していない
  4. 改造しやすい
  5. サーバ側のアーキテクチャが反映されていない
  6. ルールが統一されている

それぞれについて説明していく


1.短くて入力しやすい

読んで字のごとく、短くて入力しやすいということは、シンプルで覚えやすく理解しやすいから


2.人間が読んで理解できる

当たり前のことだが下記を意識する

  1. むやみに省略形を使わない
  2. よく使われている英単語を使う
  3. スペルミスや意味を取り違えた単語を使わない

3.大文字小文字が混在していない

  • 大文字小文字は厳密に区別されるため混在していると間違えやすくなる
  • ホスト名は通常小文字で表現されるため小文字で合わせると良い
  • getUserNameのようなケースはget_user_nameやget-user-nameとできうる。が、そもそもこういうものがでないようにする

4.改造しやすい(Hackable)

  • エンドポイントを見て他のエンドポイントを想像できるようにすることでそこまでドキュメントを見なくてもいいようにする
  • サーバ側の都合をエンドポイントに反映しない
  • バージョン管理をする

5.サーバ側のアーキテクチャが反映されていない

以下の様なのをやめる

http://api.example.com/cgi-bin/get_user.php?user=100
  • PHPで書かれてるってすぐにわかる
  • CGIとして機能していると想像できてしまう
  • こういった情報はユーザにとっては全く必要がない

6.ルールが統一されている

利用する単語、URIの構造のルールを統一する

例えば下記のようなURIが同一のサービスであった場合利用者の混乱を招くのが想像できる

// 友達の情報の取得
http://api.example.com/friends?id=100

// メッセージの投稿
http://api.example.com/friend/100/message

HTTPメソッドとエンドポイント


HTTPメソッドの一覧と役割

メソッド名 説明
GET リソースの取得
POST リソースの新規登録
PUT 既存リソースの更新
DELETE リソースの削除
PATCH リソースの一部変更
HEAD リソースのメタ情報の取得

PATCHとPUTの違い

PUTは既存リソースの更新に対して、PATCHは一部の更新

例えば1MBもあるようなリソースの更新に対してPUTですべてを更新するのではなく、PATCHで一部を更新すると良いとしている


例を使ってAPIを設計する

下記のような要件を設計するとして注意点等を説明していく

  • ユーザ登録
  • 自分の情報の取得
  • 自分の情報の更新
  • ユーザ情報の取得
  • ユーザの検索

その場合必要となるAPIは下記の表の通り

目的 エンドポイント メソッド
ユーザの一覧取得 http://api.example.com/v1/users GET
ユーザの新起登録 http://api.example.com/v1/users POST
特定のユーザの情報取得 http://api.example.com/v1/users/:id GET
ユーザの情報更新 http://api.example.com/v1/users/:id PUT/PATCH
ユーザの情報の削除 http://api.example.com/v1/users/:id DELETE

解説
  • APIとしては5つだがエンドポイントは2つだけになる
  • ユーザの検索は一覧取得APIにパラメータとして渡す
  • /v1はバージョン
  • /usersと/users/:idはユーザの集合と個々のユーザを表し、それらに対してメソッドが存在する
  • 上記の2つの概念はDBにおけるテーブル名とレコードの関係といえる

設計時の注意点

筆者が特に注意すべきと指摘する点は以下の通り

  1. 複数形の名詞を利用する
  2. 利用する単語に気をつける
  3. スペースやエンコードを必要とする文字を使わない
  4. 単語をつなげる場合はハイフン(-)を利用する

1. 複数形の名詞を利用する
  • HTTPのURIはリソースを表すものであるため名詞を利用する
  • データベースのテーブル名が複数形を用いるのが適切であるのと同様に、usersやfriendsが集合を表しているものであるので複数形を使う(単数形が使われているサービスもあるにはある)
  • 複数形は単語によって大きく変化するものがあるので注意すること(mouseがmiceみたいな)

2. 利用する単語に気をつける
  • 英単語には一つの意味で複数のものがあるため、ネイティブでないに場合は最も使われているものを探して選択するのが良い
  • このサイトがおすすめらしい ProgrammableWeb

3. スペースやエンコードが必要とする文字を使わない

こういうのはやばい。できれば空白も避けてほしい

http://api.example.com/v1/%E3%81%AA%E3%82%93%E3%82%84%E3%81%AD%E3%82%93


4. 単語をつなげる場合はハイフン(-)を利用する

単語を2つ以上つなげる場合は下記のような候補がある

http://api.example.com/v1/users/12345/prifile-image // スパイナルケース
http://api.example.com/v1/users/12345/prifile_image // スネークケース
http://api.example.com/v1/users/12345/prifileImage // キャメルケース

実際に利用されているサービスにおいてもどれを使うかはバラバラである

筆者としては、好みで決めていいとはしているが、下記の理由からスパイナルケースを推奨している

  • URI中のホストはハイフンは使えるが大文字小文字は区別されず、アンダースコアが使えない
  • ドットは特別な意味を持つ
  • 特に理由がないならホスト名と同じルールで統一すべき

最も良いのは単語をつなぎ合わせるのを極力さけること


ページネーションについて

取得数と取得位置について、相対位置と絶対位置の選択肢がある

ケース 指定方法の例
相対位置 per_page=50&page=3
絶対位置 since_id=12345&per_page=50

相対位置の場合は時間が経ち新しくデータが登録された際にクライアント側で重複が発生する可能性があるため、極力絶対位置で指定できるようにする


レスポンスデータの設計

データフォーマット

最低限JSONをサポートしていれば問題ないが、状況に応じて複数サポートすることも検討すべき

フォーマットの指定方法は下記の選択肢がある

  1. クエリパラメータで指定
  2. 拡張子をつける
  3. リクエストヘッダで指定

どれを使うかは好みの問題だが、クエリパラメータを使う方法が最も使われており、複数サポートしているケースもある

複数選ぶのであればクエリパラメータとリクエストヘッダの両方をサポートするのがおすすめ


レスポンスの内部構造


レスポンスの内容をユーザが選べるようにする

特定のリソースが大量のデータを持っている場合、すべてを返すのではなくパラメータで必要なデータを選べるようにして、必要な分だけデータを返却できるようにする

http://api.example.com/v1/users/12345?fields=name,age

省略した場合はすべて、多すぎる場合は最も利用頻度が高い組み合わせを返却すると良い


無駄なエンベロープをなくす

すべてのデータを同じ構造でくるむことをエンベロープと呼ぶ

// こういうの
{
    "header": {
        "status": "success",
        "errorCode": 0,
    },
    "response": {
    ...実際のデータ...
    }
}

エンベロープは一見便利に見えるがメタ情報はヘッダーやステータスコードで返せばよく、レスポンスのボディには実際のデータがあればよいため、冗長な表現となってしまう


できるだけデータをフラットにする

階層的な表現は必要なときだけにする

// 冗長なケース
{
    "id": 12345,
    "profile": {
        "birthday": "0123",
        "gender": "male"
    }
}

// フラットなケース
{
    "id": 12345,
    "birthday": "0123",
    "gender": "male"
}

HATEOASとREST LEVEL3 API


REST LEVEL

Martin FowlerによるとすばらしいREST APIにいたるための設計レベルには以下の様なものがある

LEVEL 内容
REST LEVEL0 HTTPを使っている
REST LEVEL1 リソースの概念の導入
REST LEVEL2 HTTPの動詞(GET,PUT等)の導入
REST LEVEL3 HATEOASの概念の導入

HATEOAS(Hypermedia As The Engine Of Application State)

HATEOASとはAPIの返すデータの中に、次に行う行動、取得するデータ等の情報をリンクとして含めること

このことで、クライアントがあらかじめエンドポイントを知らなくても次の行動を取れるようにしている

下記の例はlinkにそのデータの詳細へのURL、relにそのデータとの関連性を示している


{
    "friends": [
        {
            "name": "Saeed",
            "link": {
                "url": "http://api.example.com/v1/users/12345",
                "rel": "user/detail"
            }
        },
        ...
    ],
    "link": {
        "url": "http://api.example.com/v1/users/me/friends?sence_id=2345",
        "rel": "next"
    }
}    

REST LEVEL3のメリット

クライアントがあらかじめURIを知る必要がないためURIの変更がしやすくなり、より改造しやすいものにする必要がなくなる

REST LEVEL3のAPIを使うことでアプリケーションには入口となるURIのみをハードコートしているという事例もあるらしい

※ 筆者は絶対これにすべき、という主張はしていない


キャッシュとHTTPの仕様

キャッシュのコントロールをするためのモデルが3つあり、それぞれ説明する

  1. 期限切れモデル(Expiration Model)
  2. 検証モデル(Validation Model)
  3. 発見的期限切れ(Heuristic Expiration)

なおHTTPで用意されているのは上記の1と2


1. 期限切れモデル

  • 期限切れモデルは下記の2つのレスポンスヘッダがHTTPで用意されている
  • 上記の2つを指定した場合は新しい仕様であるCache-Controlが優先される
  • max-ageの計算にはDateヘッダを利用する
Expires: Fri, 01 Jan 2016 00:00:00 GMT // 期限切れの時間を指定するやり方
Cache-Control: max-age=3600 // ここで指定した時間がすぎたらキャッシュ切れ

2. 検証モデル

  • そのデータが更新されたかを見て確認する
  • 更新の確認には最終更新日付とエンティティタグのどちらか使う
  • クライアントは下記のようなリクエストヘッダをつけて確認を行う
  • サーバサイドはその内容をみて更新なければ304,更新あれば200でデータを返却する

// クライアントが送るヘッダ(いずれかでよい)
If-Modified-Since: Fri, 01 Jan 2016 00:00:00 GMT
If-None-Match: "06c4b240845d84a7359b1dfd0562c41c"

// サーバが返却するヘッダ(いずれかでよい)
Last-Modified: Fri, 01 Jan 2016 00:00:00 GMT
ETag: "06c4b240845d84a7359b1dfd0562c41c"

3. 発見的期限切れ

  • クライアントが自分でキャッシュする時間を決めること
  • ExpiresやCache-Controlをみてリクエストするかを決めたり、アクセスの結果一日一回くらいだからそれくらいのアクセスにする、など

Cache-Controlヘッダのまとめ

ディレクティブ名 意味
public キャッシュはプロ棋士においてユーザが異なっても共有できる
private キャッシュはユーザごとに異なる必要がある
no-cache キャッシュしたデータは検証モデルによって確認が必要
no-store キャッシュをしてはならない
no-transform プロキシサーバはコンテンツのメディアタイプやその他内容を変更してはならない
must-revalidate いかなる場合もオリジナルのサーバへの再検証が必要
proxy-revalidate プロキシサーバはオリジナルのサーバへの再検証が必要
max-age データが新鮮である期限を示す
s-maxage 中継するサーバでのみ利用されるmax-age

まとめ

  1. 覚えやすくどんな機能を持つかひと目でわかるエンドポイントにする
  2. 適切なHTTPメソッドを利用する
  3. 適切な英単語を利用し、単数複数も気をつける
  4. JSONと目的に応じたフォーマットを利用する
  5. 不要なエンベロープを使わず極力フラットにする
  6. クライアントが適切なキャッシュを返せるようなヘッダを返す

雑感

  • 前提として、この資料は今の自分に必要だと思うこと、重要だと思うことを抜粋してまとめたもの
  • 迷ったらこうしよう、という内容はキャッチアップでき、その理由も納得いくものが多かった
  • リクエストパラメータやエンドポイントに関してはなんとなく知っていた内容がちゃんと体系化して頭にまとめられた
  • エンベロープやフラットな構造はあまり意識していなかったが実際に設計する際に悩みそうなポイントだったので先に学べてよかった
  • キャッシュのコントロールは今までちゃんとやっていなかったのでこれを期に次のシステムでは考慮して活用していきたい
  • その他セキュリティ等の話もあったがそこは必要に応じて読み直す感じが良さそう
  • 全体を通して、熟練者向きではなく、初級〜中級者向けの本だと思った。まだ頭のなかに体系的にAPIかくあるべし、といった内容がない人は一回目を通してもいいかも