LoginSignup
5

More than 3 years have passed since last update.

WebAPI設計におけるデファクトってなあに?

Posted at

WebAPI設計におけるデファクトについて本を通じて学んだのでまとめました。
深いことは書いていないので、入門編ぐらいのレベル感です。

きっかけ

仕事である技術の検証を行うために、別会社のエンジニアとAPIの設計・実装を分担して行う機会がありました。

APIはそれまでも同様の技術検証で作ったことがあったので、同じ様に出来ると完全に鷹をくくっていましたが、打ち合わせの中でAPIの設計について議論することがあり、こちらで少し違和感を感じる部分があったので質問したところ「一般的にはこう作りますよね?」と言われてしまいました。

そこでハッとなりましたが、一般的をあまりよく知らない・・・完全な勉強不足でした。
詳しい知識もなく議論も出来なかったのが悔しかったので、これをきっかけに勉強することにしました。

購入した本

勉強するためにまずは本を買おうと思い、Amazonで「API」で検索してみましたが、書籍少なっ・・・。
色々悩みましたが、オライリーのWebAPI The Good Partsを購入して読んでみました。

オライリーの本って、パンチの効いた表紙だし、何か難しくて読みにくい勝手なイメージがありましたが、
この本は比較的読みやすくて楽しみながらスラスラ最後まで読む事が出来ました。
発売されたのは2014年ですが色あせないオススメの1冊だと思います。

美しいWebAPIのススメ

この本の冒頭に、WebAPIは美しくあるべきということが書かれています。
プログラムの場合、美しいコードと言うことがあると思いますが、それと同じです。
ここでいう美しいとは以下の様な意味合いを含みます。

  • よく考えられている
  • わかりやすく整理されている
  • 無駄がない

美しいWebAPIにすると
①使いやすくて、②変更しやすくて、③頑強で、④恥ずかしくない !!
嬉しい事三昧です。

では、WebAPIを美しくするにはどうすればいいのか・・・?

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

これが、WebAPIを設計する上でかなり重要な原則になります。

WebAPI設計のデファクトを知る

WebAPI設計の普通をよく知らなかったので、本書を通じてデファクトを知ることが出来ました。
いくつか抜粋し以下にまとめてみました。

URI=操作する対象

WebAPIはHTTPを使いアクセスを行うため、WebAPIを設計する上でHTTPにおける考え方やルールが非常に参考になります。HTTPにおいて、URIはリソースですが、WebAPIにおいては操作する対象となります。

そのため、URIは動詞ではなく原則として名詞でなければなりません。
(あんまり意識した事がなかったので、これを知った時はまあまあの衝撃でした...)

さらに、URIで使う単語についても色々気をつけなければなりません。

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

一言で言えば、「覚えやすくて、どんな機能を持つURIなのかがひと目でわかる」というのが重要です。

HTTPメソッド=何をするか

先ほどのURIに対して、何をしたいのかをHTTPメソッドで記述します。
そのため、URIとHTTPメソッドはペアで考える必要があります。

具体的には、リソースを取得したいのであればGET、削除したいのであればDELETEを使うというような感じです。メジャーなGETやPOST以外にも色々あります。

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

【参考】HTTP リクエストメソッド(MDN web docs)
https://developer.mozilla.org/ja/docs/Web/HTTP/Methods

例えば、ユーザ一覧を取得したい場合に、プログラムのメソッドのような感じで/getUsersとか書いてしまいそうになりますが、これは正しくありません。/userというようなエンドポイントにしておき、GETメソッドで取得します。
(この本を読むまでは、API設計時におけるエンドポイントの命名は、プログラムのメソッドの命名と同じようなもんだと思っていましたが完全に間違っていたようです・・・)

ステータスコードを正しく使う

ステータスコードはクライアントからのリクエストがどう処理されたのかを表し、HTTPレスポンスに含まれています。数字によって意味合いが異なり、細かく定められています。

ステータスコード 意味
100番台 情報
200番台 成功
300番台 リダイレクト
400番台 クライアント再度に起因するエラー
500番台 サーバサイドに起因するエラー

【参考】HTTPステータスコード(MDN web docs)
https://developer.mozilla.org/ja/docs/Web/HTTP/Status

重要なのはクライアントからのリクエストが成功した場合にのみ200を返すと言う事です。
一見当たり前の様ですが・・・なんでもかんでも200を返す、みたいな設計はダメと言う事です。

ステータスコードを見て、リクエストが成功したかどうかを判断しているものも多いため、
適切なコードを返した方が、クライアントがエラーを正しく認識してくれる可能性が高くなります。

メディアタイプも正しく使おう

リクエストやレスポンスでは、送信するデータ本体の形式を表すためにメディアタイプを指定します。
メディアタイプとは、そのデータがどんな形式なのかを表すものです。

メディアタイプ データ形式
text/plain プレーンテキスト
text/html HTML文書
application/json JSON文書
image/jpeg JPEG画像
video/mp4 MP4動画ファイル

【参考】MIMEタイプ(MDN web docs)
https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types

先ほどのステータスコードと同じように、クライアントの多くは、メディアタイプを使いデータ形式を判断しているものも多いため、指定を間違えると、正しくデータを読み出せないケースが出てきます。

実は・・・それ以外にも、セキュリティ上大変重要な意味を持ちます。

例えば、以下の様なJSONデータを、誤ってHTML(メディアタイプ:text/html)で返却してしまったとします。すると、HTMLとして解釈され意図せずにJavaScriptが実行されてしまいます。

{"data":"<script>alert('hoge');</script>"}

これを防ぐためには、メディアタイプとしてJSON(application/json)をきちんと返す必要があります。

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

レスポンスの内容はカチッと決めずに、ものによっては自由度を持たせ方がいいそうです。

例えば、レスポンスのフォーマットの場合だと、JSONで返すのかXMLで返すのかクエリパラメータで選べる様にするといった様な感じです。(もはやJSONがデファクトになりつつはありますが...)

例)XMLフォーマットでユーザ情報を取得したい場合
/users?format=xml

あと、APIで返すレスポンスデータを決定する際に重要となることは、APIのアクセス回数がなるべく減るようにすることです。出来る限り多くのデータを返せば、APIのアクセス回数は減りますが、多すぎるとクライアントが大量のデータを受け取ることになり不必要なものも含まれてしまいます。

このような場合もクエリパラメータを使って、取得したい項目をユーザが指定できる様にすると柔軟性が出ます。

例)ユーザ12345の情報について名前(name)と年齢(age)だけ欲しい場合
/users/12345?fields=name,age

APIをバージョンで管理する

APIは一度公開して終わりではなく、機能追加やバグ修正などにより更新が必要になるという事が往々にしてあります。ただ、APIを急に変更してしまうと、現在利用しているクライアントに影響が出てしまうため慎重な対応が必要です。

そこでよく使われるのが、旧バージョンと新バージョンのAPIを別のURIでそれぞれ公開するという手法です。さらに、各々のバージョンがわかる様に、URIにv1v2のようにバージョン情報を含めるのが一般的となっているようです。

ソフトウェアの世界では、1.2.3のように数字をドットでつないだものがよく使われ、バージョニングの考え方としてセマンティックバージョニングというものもあるらしいです。

  • メジャーバージョン:後方互換性のない変更が行われた際に増える
  • マイナーバージョン:後方互換性のある機能変更、あるいは特定の機能が今後廃止される事が決まった場合に増える
  • パッチバージョン:APIに変更がないバグ修正などを行ったときに増える

【参考】セマンティックバージョニング
https://semver.org/lang/ja/

APIの世界では、細かくバージョンを設定して公開することは少なく、メジャーバージョンまでを含めるのが主流のようです。

QiitaAPIはどうなっている?

実際どうなっているのか、QiitaのAPIで確認してみます。

QiitaのAPIは以下のサイトで公開されています。
https://qiita.com/api/v2/docs

エンドポイント名

例えば、GETで取得できるものはこんな感じになっています。

対象 URI 単語
投稿 /api/v2/items items
いいね /api/v2/items/:item_id/likes likes
コメント /api/v2/comments/:comment_id comments
タグ /api/v2/tags tags
ユーザ /api/v2/users users

ちゃんと名詞になっています。

ステータスコード

スクリーンショット 2020-01-19 18.38.49.png

利用するHTTPメソッドによって細かく分かれています。
201や204などあまり馴染みないものがありますが、、、201は「Created」で「リクエストは成功し、新しいリソースが作られた」と言う意味を表します。一般的にPOSTでデータ追加した時に返す事が多い様です。204は「No Content」で「コンテンツなし」と言う意味を表します。DELETEでデータ削除した時に返す事が多い様です。

バージョン管理

URIの共通部分は「qiita.com/api/v2/」となっており、v2というバージョン情報が含まれています。
試しに、前バージョンであろうドキュメントページにアクセスすると、以下のページが返ってきます。

スクリーンショット 2020-01-20 22.15.28.png

ちゃんと新しいバージョンv2に誘導してくれます。
さらに、前バージョンv1に対し、GETリクエストを投げ投稿一覧を取得してみます。

$ curl --dump-header - https://qiita.com/api/v1/items
HTTP/1.1 404 Not Found
Date: Mon, 20 Jan 2020 13:21:23 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Server: nginx
Vary: Origin
X-Runtime: 0.003041
Strict-Transport-Security: max-age=2592000
X-Request-Id: 5218a24d-331f-4352-b1eb-0ddf9e521994

{"message":"Qiita API v1 is no longer available. Please use Qiita API v2 instead.","type":"not_found"}

404のエラーが返ってきますが、JSONレスポンスのmessageに「v1ではなくv2を使ってね」と書いてあります。
このように、APIのバージョンアップを実施した場合は、旧バージョンへアクセスしてきたユーザに対する配慮も忘れずに対応すると親切です。

こうしてHTTPヘッダを見ると、nginx使っているんだな〜とか分かって少し面白いですね。
ちなみに、現バージョンv2へ同様のリクエストを実施した場合は、以下の様なHTTPヘッダが返ってきます。

$ curl --dump-header - https://qiita.com/api/v2/items 
HTTP/1.1 200 OK
Date: Mon, 20 Jan 2020 13:26:53 GMT
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: nginx
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Download-Options: noopen
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: strict-origin-when-cross-origin
Link: <https://qiita.com/api/v2/items?page=1>; rel="first", <https://qiita.com/api/v2/items?page=2>; rel="next", <https://qiita.com/api/v2/items?page=25062>; rel="last"
Total-Count: 501235
ETag: W/"0767e24250b0fc354e1e270a70d0b835"
Cache-Control: max-age=0, private, must-revalidate
Rate-Limit: 60
Rate-Remaining: 54
Rate-Reset: 1579529574
Vary: Origin
X-Runtime: 0.352367
Strict-Transport-Security: max-age=2592000
X-Request-Id: cb2b8a25-0cad-4aa3-863b-63b79a3e67d3

こうして見てみると結構な数・・・1個1個調べるだけでも勉強になりそうです。

【参考】HTTP ヘッダー(MDN web docs)
https://developer.mozilla.org/ja/docs/Web/HTTP/Headers

レスポンスの内容

例えば、記事一覧を取得する場合は以下のAPIを呼び出します。

スクリーンショット 2020-01-20 23.25.56.png

ドキュメントによると、以下の様なレスポンスが返ってくるそうです。

[
  {
    "rendered_body": "<h1>Example</h1>",
    "body": "# Example",
    "coediting": false,
    "comments_count": 100,
    "created_at": "2000-01-01T00:00:00+00:00",
    "group": {
      "created_at": "2000-01-01T00:00:00+00:00",
      "id": 1,
      "name": "Dev",
      "private": false,
      "updated_at": "2000-01-01T00:00:00+00:00",
      "url_name": "dev"
    },
    "id": "4bd431809afb1bb99e4f",
    "likes_count": 100,
    "private": false,
    "reactions_count": 100,
    "tags": [
      {
        "name": "Ruby",
        "versions": [
          "0.0.1"
        ]
      }
    ],
    "title": "Example title",
    "updated_at": "2000-01-01T00:00:00+00:00",
    "url": "https://qiita.com/yaotti/items/4bd431809afb1bb99e4f",
    "user": {
      "description": "Hello, world.",
      "facebook_id": "yaotti",
      "followees_count": 100,
      "followers_count": 200,
      "github_login_name": "yaotti",
      "id": "yaotti",
      "items_count": 300,
      "linkedin_id": "yaotti",
      "location": "Tokyo, Japan",
      "name": "Hiroshige Umino",
      "organization": "Increments Inc",
      "permanent_id": 1,
      "profile_image_url": "https://si0.twimg.com/profile_images/2309761038/1ijg13pfs0dg84sk2y0h_normal.jpeg",
      "team_only": false,
      "twitter_screen_name": "yaotti",
      "website_url": "http://yaotti.hatenablog.com"
    },
    "page_views_count": 100
  }
]

1つの投稿に結構な数の項目が含まれています。
現状では、呼び出し元で取得項目を選択できないため、このように全情報が返ってきます。

件数が多くなると結構なデータ量になりそうですが、1回の呼び出しで取得できる件数(per_page)の最大が100件と制限されているので、データ量は若干抑えられるような気がします。

まとめ

まだまだ勉強中ではありますが、本書を通じてAPIのデファクトを知る事ができました。
詳細についてはオライリーのWebAPI The Good Partsを読んでみて下さい。

Webにも関連ドキュメントが多数あるのでそちらも参考になりそうです。

新たなWebAPI規格の1つであるGraphQLについてフワッとしか知らないので、そろそろ勉強せねば・・・。

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
5