LoginSignup
231

More than 5 years have passed since last update.

Web API: The Good Partsを読んだので「レスポンスデータの設計」についてまとめた

Last updated at Posted at 2015-07-25

はじめに

APIの勉強のために、Web API: The Good Partsを読みました。平易な日本語で書いてあるので、読みやすかったです。
とはいえ、何度も本を読み返すのは大変なので、自分用まとめも兼ねて書こうと思った次第です。

1個1個まとめていくと結構な量があるので今回は「レスポンスデータの設計」についてまとめました。
本でいうと3章に書いてあります。

この記事も参考に

Web API: The Good Partsの他のまとめ記事もここに載せておきます。

記事を書く順番は章ごとではなく結構バラバラです。

レスポンスデータはプログラムで処理しやすいものにする

Web APIはHTMLの代わりによりプログラムで処理をしやすいデータ形式を返すWebページの一種なので、レスポンスデータはできるかぎりプログラムで処理をしやすいものにする必要があります。
どうすればよりプログラムで処理をしやすいレスポンスデータとなるか(設計)についてまとめていきます。

データフォーマット

まずどういうデータフォーマットにするか考えます。
具体的に言うとJSONやXMLのことですね。

実際のところ昨今はJSONにデフォルトで対応し、需要や必要があればXMLに対応するというのがスタンダードのようです。
JSONがデータフォーマットのデファクトスタンダードになっていて、主要なWebサービスでもJSONのみ対応か、JSON+XMLがほとんどになっています。

JSONがXMLより広まった主な理由は

  • JSONのほうがシンプルで同じデータを表すのにサイズが小さくてすむ
  • Webの世界においてはクライアントのデフォルト言語であるJavaScriptとの相性がとてもよい

といった理由があげられます。個人的には特に後者はJavaScriptJSONのほうがXMLより扱いやすかったので非常に納得がいくものです。

指定方法

データフォーマットの指定方法には一般的に3つの方法が使われています。

  • クエリパラメータを使う
  • 拡張子を使う
  • リクエストヘッダでメディアタイプを指定する

具体的には以下の様な形式です。

https://api.example.com/v1/users?format=xml
https://api.example.com/v1/users.json
GET /v1/users
HOST: api.example.com
Accept: application/json

どれを採用するかは好みによります。
リクエストヘッダが一番行儀の良い書き方となりますが、やや敷居が高いという点があります。
主要なWeb ServicesでのAPIの使われた方を見てみると、リクエストヘッダはほぼ採用されず、クエリパラメータを使う方法が最もよく使われています。

筆者によれば、1つだけを選ぶならURIでクエリパラメータを使う方法、複数選ぶならクエリパラメータを使う方法とリクエストヘッダでメディアタイプを指定する方法をサポートするそうです。
理由には、クエリパラメータは初心者にもやさしい方法かつ省略可能という点、URIをリソースとしてみなした時にもあっているということがあげられます。

データの内部構造の考え方

データのフォーマットを決めたら、どんなデータを返すかを決めていきます。
まず考えるべきことは、 APIのアクセス回数がなるべく減るようにすること です。そのためにはそれぞれのAPIの利用場面(ユースケース)をきちんと考えることが重要になります。

例えば、ソーシャルネットワークの友人一覧を取得するAPIについて考えてみます。
その時APIの返す結果が以下の様だったらどうでしょうか。

{
    "friends": [
        100,
        500,
        700,
        ...
    ]
}

友達のIDだけを返しているので、データサイズは確かに小さくなっています。
しかし実際の利用場面を考えてみましょう。
クライアント側ではIDだけ表示したいということはないでしょう。
名前やサムネイル画像、性別などの情報が必要になると思います。
もしAPIがIDだけを返すようなものだったら、最低2回APIにアクセスする必要が出てきてしまいます。

ですので、例えば以下のようにすると使いやすいでしょう。

{
    "friends": [
        {
            "id": 200,
            "name": "Taro Tanaka",
            "profileIcon": "http://image.example.com/profile/200.png",
            ...
        },
        {
            "id": 400,
            "name": "Hanako Yamada"
            "profileIcon": "http://image.example.com/profile/400.png",
            ...
        }
    ]
}

WebAPIはその名の通りアプリケーションのインターフェイスであるので、特性を踏まえた上で利用者が使いやすい構造を検討するべきです。
できるかぎり少ないアクセス回数ですむAPI設計を心がけるようにしましょう。

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

先ほどの良い例のようにすれば何度も叩く必要はなくなりますが、今度は必要以上に大量のデータをクライアントが受け取らなくてはならなくなってしまいます。
たとえば情報として、ユーザーの名前とアイコンだけ必要としているのに、他の情報(職歴や出身地など)も一緒にとってきたらその分データが大きくなってしまいます。

この時によく取られる手法が、取得する項目を 利用者が選択可能にするというものです。クエリパラメータを使って、たとえばユーザー情報のうち名前と年齢を取得したい、といったことを指定することができるようにします。

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

さまざまなAPIがこのようなフィールド名に対応していて、クエリパラメータを省略した場合は全部返すようになっていたりもします。
あらかじめいくつかの取得する項目の量の異なるセットを用意しておいて、その名前を指定させるという方法があります。
このセットのことをレスポンスグループとAmazonのProduct Advertising APIでは呼んでいます。

エンベロープ

続いてデータ全体の構造について考えます。
直訳すると封筒のことですが、APIのデータ構造の文脈で言うと、すべてのデータ(レスポンスやリクエスト)を同じ構造でくるむことをいいます。
例えば以下のような感じです。

{
    "header": {
        "status": "success",
        "errorCode": 0
    },
    "response": {

    }
}

データ構造をどうするか(階層的 or フラット)

JSON(やXML)では階層構造を表すことができるので、同じデータでもフラットに表すことも階層的にあらわすこともできます。
例えば2人でのメッセージのやりとりを表現するデータがあったとして、階層的にもフラットにも表せます。

{
    "id": 111,
    "message": "Hi",
    "sender": {
        "id", 1234,
        "name", "Taro"
    },
    "receiver": {
        "id", 3456,
        "name", "Kenji"
    }
}
{
    "id": 111,
    "message": "Hi",
    "sender_id": 1234,
    "sender_name": "Taro",
    "receiver_id": 3456,
    "receiver_name": "Kenji",
    ...
}

どちらがいいかは状況次第のようです。
Google JSON Style Guideでも「なるべくフラットにしたほうがよいけど、階層構造を持ったほうがわかりやすい場合もある」とやや曖昧な表記になっています。上記の例だったら階層構造にしたほうがわかりやすいかもしれません。

一方、以下のような場合はどうでしょう。

{
    "id": 123,
    "name": "Taro",
    "profile": {
        "birthday": 0410,
        "gender": "male",
        "languages": [ "ja", "en" ]
    }
}

これはprofileと単に複数項目をまとめたいだけなのに階層構造にしてしまっています。見た目にもあまり違いはないので、これはフラットで表現したほうがいいでしょう。

{
    "id": 123,
    "name": "Taro",
    "birthday": 0410,
    "gender": "male",
    "languages": [ "ja", "en" ]
}

配列とフォーマット

JSONはトップレベルに配列もオブジェクトのどちらも置けます。
そこまで大きな差はないといえますが、筆者はオブジェクトのほうがおすすめとのこと。主張は以下のとおり。

メリット

  • レスポンスデータが何を示しているものかがわかりやすくなる
  • レスポンスデータをオブジェクトに統一することができる
  • セキュリティ上のリスクを避けることができる

1番目はキーがあることで何のことか分かりやすくなるということで、2番目はルールを統一しておいたほうがいいということですね。
3番目はJSONインジェクションという脆弱に対するリスクへの回避のためのようです。(配列の場合はリスクが高まる)

配列の件数、続きがある場合の対応

ページネーションがある場合、続きがあるかどうかを知りたい時があると思います。
続きがあるのかという情報を例えば「hasNext」といった名前でいれてあげれば、必ずしも全体件数を知る必要はないでしょう。

各データのフォーマット

各データの名前

命名の考え方は以下のとおりです。

  • 多くのAPIで同じ意味に利用されている一般的な単語を用いる
  • なるべく少ない単語数で表現する
  • 複数の単語を連結する場合、その連結方法はAPI全体を通して統一する
  • 変な省略形は極力利用しない
  • 単数形/複数形に気をつける

これは2.2のWeb API: The Good Partsを読んだので「良いURI」についてまとめたの命名にも書いたとおりです。
実際の事例を見るために、Programmable Webも参考になると思います。

レスポンスデータの設計

既に述べたように、APIは内部で持っているDBのテーブル構造をそのまま反映する必要はありません。利用場面を考えてシンプルな設計を目指す必要があります。

エラーの表現

APIではエラーに対する処理をして上げる必要があります。もしAPIでエラーが出ない場合は原因も分からず、使いづらいAPIとなってしまうでしょう。

ステータスコードでエラーを表現する

ステータスコードについてはWeb API: The Good Partsを読んだので「HTTPの仕様」についてまとめたを参考にしてみてください。

成功した場合しか200番台を返してはいけないなど基本的なルールをおさえることが大事です。

エラーの詳細をクライアントに返す

ステータスコードでエラーを返せば十分かというとそうではありません。実際にエラーの詳細がわからないと結局どうすればいいんだよ、となってしまうからです。
エラーの内容を返す方法は大きく分けて2つあります。

  • HTTPのレスポンスヘッダに入れて返す方法
  • レスポンスボディで返す方法

です。

レスポンスヘッダには独自定義したヘッダ項目にエラー内容を書いておき、レスポンスボディにはエラー情報を以下のように格納しておくというものです。

    "error": {
        "code": 2013,
        "message": "bad authentication token",
        "info": "http:example.com/api/v1/authentication"
    }

実際にサービスの例を見てみるとほとんどはレスポンスボディにいれているので、基本的にレスポンスボディにデータを入れる方法で問題ないでしょう。

エラー詳細情報は何を入れるか

エラー情報として返すべき情報として、

  • エラーの詳細コード(API提供側で独自にエラーコードに提供したコード)
  • 詳細情報へのリンク

があれば、最低限利用者に情報を伝えることが出来ます。

エラーのときにHTMLが返ることを防ぐ

リクエストボディがHTMLにならないように注意しましょう。
404,500,503でよく見られるようです。
適切なフォーマットで返るようにしておくのがいいでしょう。

メンテナンスとステータスコード

APIをメンテナンスで止めるのは第三者への影響も多く極力避けるべきでしょう。
ですが、どうしてもメンテナンスしなくてはいけないときはステータスコードとして503を返し、サービスが停止していることを伝える必要があります。
そのときは予期しないサービス停止ではなく、スケジュールされたものであり、終了予想時刻がわかっているならそれを伝えるべきです。

エラーコードとエラーメッセージを返すだけでなく、Retry-AfterというHTTPヘッダを使っていつメンテナンスが終わるのかを示します。
Retry-Afterの値には、具体的な日付か、現在時刻からアクセス可能になるまでの秒数が入ります。

503 Servicee Temporary Unavailable
Retry-After: Mon, 2, Dec 2013 03:00:00 GMT

意図的に不正確な情報を返したい場合

セキュリティやその他の理由でエラーを曖昧にしたいケースがあるでしょう。
たとえばSNSでブロックされた場合。
正確にエラーを返すなら、認可のエラーで403になりますがそれだと相手にブロックされたことがわかっていまいますよね。
したがってそういうときは、「ブロックされた側から見るとブロックした側はもはや存在しないと同義」とみなして404を返すといったことも可能です。

他にもログインの際にメールアドレスとパスワードを送信する際に、間違った時に詳細にエラーを返しすぎると、悪意を持った不正ユーザーに親切な情報を与えることになってしまいます。

ここはユーザー体験の向上にも重なる部分なので、仕様を決めるときにどうすればいいのか決めるべきでしょう。
なお開発時にはあいまいにする必要がないので、正確な情報を返し、本番環境ではあえて曖昧な答えを返すというのもありでしょう。

おわりに

レスポンスデータの設計についてどうやってすすめていけばいいかまとめていきました。
設計、命名、データ構造、エラー処理など基本的な考え方をまとめました。
特にJSONをどういう風な構造にしていけばいいかというのは参考になりました(以前の私に教えてあげたい・・)。
もし、参考になるところがありましたら幸いです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
231