Web APIの設計の本「Web API The Good Parts」の紹介

書籍
ユウ
ユウ

こんにちは、ユウです!

本記事ではWeb APIの設計に関する本を紹介します

仕事でWeb APIを設計することがあるけれど、しっかりと設計の仕方を学んだことがない人もいるかと思います。私もそうでした。

先日、Web APIを設計をしていて、「この場合、どうするんだ?」など疑問が浮かぶケースが多かったため、良い機会だと思い、本を買って勉強することにしました。

本屋で何冊か見てみて、「Web API The Good Parts」という本が良さそうだったので、買って読みました。

実際、読んでみて良かったので、本記事ではこの本を紹介したいと思います。

この記事は以下のような人におすすめ!

  • Web APIの設計を学べる本を探している
  • 「Web API The Good Parts」でどんなことが学べるか知りたい

この本を選んだ理由

本屋で何冊か見てみたのですが、以下の理由からこの本が良いと思いました。

  1. API設計について一通り書いてあり、全体感が掴める
  2. 分量が200ページ程度と非常にコンパクトにまとまっている
  3. 自分の知りたい内容(特にエラーレスポンス周りの設計)が書いてあった

仕事でAPI設計をしながら短時間で勉強したいと思っていたので、上記2は個人的には刺さりました(実際、土日の2日程度で読むことできました)。

ちなみに、「Web APIの設計」(Arnaud Lauret (著), 株式会社クイープ (監修, 翻訳))という本も良さそうでしたが、分量がやや多く、読むのに時間がかかりそうだったので、今回はやめました。

本の内容紹介

本の内容を一部紹介します。以下の内容を読んで、少しでも勉強になることがあれば、この書籍をチェックしていただければと思います。

Web APIは美しく設計する

Web APIは美しく設計することが大切。その理由は以下:

  • 設計の美しいWeb APIは使いやすい
  • 設計の美しいWeb APIは変更しやすい
  • 設計の美しいWeb APIは頑強である
  • 設計の美しいWeb APIは恥ずかしくない

Web APIを設計するには以下を心がけると良い:

  • 仕様が決まっているものに関しては仕様に従う
  • 仕様が存在していないものに関してはデファクトスタンダードに従う

エンドポイントの設計

良いエンドポイント(URI)を設計する上での重要な原則:

覚えやすく、どんな機能を持つURIなのかひと目でわかる

以下、覚えやすいURIの特徴を紹介します。

①短く入力しやすいURI

NGな例

http://api.example.com/service/api/search

「api」がホスト名とパスに重複して含まれている。また、「service」と「api」が概念的にやや重複している。

OKな例

http://api.example.com/search

これでも十分に意味が伝わる。同じことを表現しているなら短く、シンプルな方が覚えやすく、理解しやすく、間違えにくい。

②人間が読んで理解できるURI

NGな例

http://api.example.com/sv/u/

「sv」、「u」が何を表しているか不明。むやみに省略形は使わないこと。

③大文字小文字が混在していないURI

NGな例

http://api.example.com/Users/12345
http://example.com/API/getUserName

上記のように大文字小文字が混在すると、APIがわかりづらく、間違えやすくなる。

標準的に選択されるのは小文字なので、小文字に統一するとよい。

④改造しやすい(Hackableな)URI

OKな例

http://api.example.com/v1/item/12346

上記のAPIだとitemのidが12346で、12346の部分を変えると他のアイテムの情報にアクセスできるであろうことが容易に想像つく。よって、APIを使う側の利用負担、バグを起こす可能性が減る。

NGな例

# idの範囲: 1~400000
http://api.example.com/v1/item/alpha/:id

# idの範囲: 400001~500000
http://api.example.com/v1/item/beta/:id

# idの範囲: 500001~700000
http://api.example.com/v1/item/gamma/:id

# idの範囲: 700001~
http://api.example.com/v1/item/delta/:id

idの範囲によってAPIが異なる例。

これだと、APIを使う側は大変だし、バグも起きる可能性が上がる。

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

NGな例

http://api.example.com/cgi-bin/get_user.php?user=100

このAPIがPHPで書かれていて、CGIとして動作することがわかってしまう。

これらの情報はAPIを使う側からは一切不要な情報。また、サーバの脆弱性を突いて攻撃を受けやすくなる。

⑥ルールが統一されたURI

NGな例

# 友達の情報の取得
http://api.example.com/friends?id=100
# メッセージの投稿
http://api.example.com/friend/100/message

友達の情報を取得するAPIでは、friendsと複数形になっており、IDはクエリパラメータで指定している。一方、メッセージを投稿するAPIでは、friendと単数形になっており、IDはURIのパスに入っている。ルールが統一されていないと、クライアントを実装する場合に混乱を招きやすく、バグの温床になる。

OKな例

# 友達の情報の取得
http://api.example.com/friends/100
# メッセージの投稿
http://api.example.com/friends/100/message

レスポンスデータの設計

①APIのアクセス回数がなるべく少なくなるようにする

NGな例

{
    "friends": [
        234232,
        93734,
        197322,
        ...
    ]
}

レスポンスデータが友達のIDのみを含むAPI。

APIを使うクライアントは、ユーザの情報(例:名前、プロフィールアイコン、性別)を画面に表示したいはず。上記のAPIだと、取得したIDをもとにもう一度ユーザの情報を取得するAPIにアクセスしないといけない。使い勝手が悪い。

なお、1つの作業を完了するために複数回アクセスを必要とするAPIの設計は「Chatty(おしゃべりな)API」と呼ばれる。

OKな例

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

レスポンスがこの形式だと、一度のAPIアクセスで必要な情報を取得できる。

②不要なエンベロープを入れない

NGな例

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

「エンベロープ」は「封筒」の意味で、APIのデータ構造の文脈では、すべてのデータ(レスポンスやリクエスト)を同じ構造でくるむことをいう。

エンベロープは基本、不要。

理由は、Web APIはHTTPを利用しており、HTTPのヘッダにはさまざまな情報を入れることができ、エラーかどうかの判断もステータスコードを返すことでできるため。つまり、上記の例のheaderの情報はHTTPのヘッダにすでに含まれている。

③できるだけフラットにする

レスポンスを階層的にするかフラットにするかはケースバイケース。

以下は階層的にしてもよい例。

# 階層的に表す場合
{
    "id": 3342124,
    "message": "Hi!",
    "sender": {
        "id": 3456,
        "name": "Taro Yamada"
    },
    "receiver": {
        "id": 12912,
        "name": "Kenji Suzuki"
    },
    ...
}

# フラットに表す場合
{
    "id": 3342124,
    "message": "Hi!",
    "sender_id": 3456,
    "sender_name": "Taro Yamada",
    "receiver_id": 12912,
    "receiver_name": "Kenji Suzuki"
    ...
}

上記の例だと、レスポンスを階層的にすることで、クライアントは「sender」と「receiver」を同じユーザーのデータとして扱える。また、keyにsenderやreceiverという接頭辞をつけなくて済み、JSONのサイズが少なくなる。

一方、以下の場合は、レスポンスを階層的にする意味はない(むしろ階層的にするとJSONのサイズが大きくなる)ので、フラットにした方がよい。

# 階層的に表す場合
{
    "id": 23245,
    "name": "Taro Yamada",
    "profile": {
        "birthday": 3456,
        "gender": "male",
        "langage": ["ja", "en"]
    }
    ...
}

# フラットに表す場合
{
    "id": 23245,
    "name": "Taro Yamada",
    "birthday": 3456,
    "gender": "male",
    "langage": ["ja", "en"]
    ...
 }

④配列でなくオブジェクトにする

以下のように、APIで配列を返すケースでは、配列をそのまま返す方法と、オブジェクトで包んで返す方法がある。

# 配列をそのまま返す場合
[
    {
        "id": 234232,
        "name": "Taro Tanaka",
        "profileIcon": "http://image.example.com/profile/234232.png",
            ...
    },
    {
        "id": 93734,
        "name": "Hanako Yamada",
        "profileIcon": "http://image.example.com/profile/93734.png",
            ...
    },
        ...
]

# オブジェクトで包んで返す場合
{
    "friends": [
        {
            "id": 234232,
            "name": "Taro Tanaka",
            "profileIcon": "http://image.example.com/profile/234232.png",
            ...
        },
        {
            "id": 93734,
            "name": "Hanako Yamada",
            "profileIcon": "http://image.example.com/profile/93734.png",
            ...
        },
        ...
    ]
}

筆者としては、オブジェクトで包んで返す方法を推奨。理由は以下:

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

1は文字通りの意味。

2はトップレベルが配列だったりオブジェクトだったりAPIによって変わると、クライアントで取得して前処理を行う場合などに面倒になる可能性がある。

3はトップレベルが配列であるJSONはJSONインジェクションという脆弱性に対するリスクが大きくなる。

まとめ

以上、本記事では「Web API The Good Parts」を紹介させていただきました。

本記事を少しでも勉強になることがあればぜひ本書籍でチェックしていただければと思います。

本書籍は一度読んでおいて損はないかと思います。

プロフィール
この記事を書いた人

30代半ばで未経験でプログラマーに転職し、日々奮闘中です
プログラミング、AI、NLP、キャリア関連などで少しでも役に立てる情報を発信していきます

ユウをフォローする
書籍
ユウをフォローする

コメント

タイトルとURLをコピーしました