$30 off During Our Annual Pro Sale. View Details »

RESTful API の設計のキホン

Cside
October 12, 2016

RESTful API の設計のキホン

2016/10/12 社内勉強会で使ったスライドを社外向けに一部加筆訂正したもの

Cside

October 12, 2016
Tweet

More Decks by Cside

Other Decks in Programming

Transcript

  1. RESTful API の設計のキホン
    2016/10/12 Hiroki Honda
    @Cside_

    View Slide

  2. なぜこの会を開いたか
    ● 自分の関わった某案件で、API の新エンドポイントのインターフェイスをレビューす
    るときに「こういう風に直して下さい」と指示はしてきたけれど、「なぜそのように直さ
    なければならないのか」までちゃんと説明しきれてなかった
    ○ 納得できてない人もいると思う
    ○ なのでちゃんと説明したい
    ○ そして、誰でも設計できるようになってほしい(重要)

    View Slide

  3. おことわり
    ● API の設計に正解はなく、これから話す指針はあくまで「私個人の考える Good
    Practice 」であることをご了承下さい
    ○ マウンティングやめてね … 。
    ○ なるべく、他にどういう流派があるかはあわせて説明するようにします

    View Slide

  4. 第一章
    REST と RPC

    View Slide

  5. Web API のスタイル
    ● REST スタイル
    ● RPC スタイル
    が主なもの。

    View Slide

  6. REST スタイルの特徴
    ● REST スタイルの API 設計では ROA( Resource Oriented Architecture )という
    手法が広く知られている
    ● Web API を RESTful にする == ROA にそって設計する
    ● なので ROA の特徴をしっかり理解するのが重要

    View Slide

  7. ROA の 4 つの特徴
    ● アドレス可能性
    ○ リソースがURIを通して表現できること
    ● ステートレス性
    ○ APIリクエストのためのHTTPリクエストがすべて分離・独立していること(前の
    リクエストに影響されたりしない)
    ● 接続性
    ○ リソースは別のリソースとの関連を表すリンクを持ちうること
    ● 統一インタフェース
    ○ HTTP メソッドを用いてリソースを参照/更新すること
    ○ 後述

    View Slide

  8. リソースとは
    ● URI を持ったデータのこと

    View Slide

  9. リソースの例
    ● 例えば、モバゲーにおける僕のプロフィールというリソースは以下の URI を持って
    いる
    ○ /api/restful/v1/people/32592054/@self

    View Slide

  10. リソースの例
    ● GET すると以下のようなレスポンスが返ってくる
    (※ 形式は簡略化しています)
     GET /api/restful/v1/people/32592054/@self
    HTTP/1.1 200 OK
     {
      "id" : 32592054,
      "nickname" : "ほんだ",
      "thumbnailUrl" : "http://sp.mbga.jp/img_u/10000/0.0.gif",
      "profileUrl" : "http://sp.mbga.jp/_u?u=10000",
      "hasApp" : true
     }
    このオブジェクトが
    リソース。

    View Slide

  11. ROA の統一インターフェイス
    HTTP メソッドを用いてリソースを取得/更新する。
    ● GET: リソースの取得
    ● HEAD: リソースの取得。HTTP ヘッダのみを返す
    ● POST: リソースの新規作成
    ● PUT: 既存リソースの置き換え
    ● PATCH: 既存リソースの差分更新
    ● DELETE: 既存リソースの削除

    View Slide

  12. RPC スタイル
    ● XML-RPC, JSON-RPC など
    ● 一言で言うと、制約が少なく、中央集権型

    View Slide

  13. RPC スタイル
    ● RPC エンドポイント、メソッド名、リクエストパラメータ だけあれば利用できる
    リクエスト例
    POST /rpc-endpoint
    Content-Type: application/json
    { "jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5] }

    View Slide

  14. RPC スタイル
    ● RPC エンドポイント、メソッド名、リクエストパラメータ だけあれば利用できる
    リクエスト例
    POST /rpc-endpoint
    Content-Type: application/json
    { "jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5] }
    エンドポイントは 1 個しか
    ない場合が多い
    リソースの操作方法は HTTP メソッ
    ドでなくパラメータで指定
    基本全部
    POST

    View Slide

  15. RPC スタイル
    ● エンドポイントが 1 つしかない場合が多い
    ○ リソースごとにユニークな URI を割り当てる REST と比べると対照的

    View Slide

  16. REST か RPC か
    ● REST
    ○ 特徴
    ■ ルール(制約)によって自明なコンセンサスが生まれ、システムを異なる
    人が分担して設計したとしても、一定の一貫性を担保できる
    ○ 向いているケース
    ■ 複数のエンジニアが API の設計をする場合

    View Slide

  17. REST か RPC か
    ● RPC
    ○ 特徴
    ■ 柔軟性が高く、中央集権型
    ○ 向いているケース
    ■ 限定されたエンジニアが設計をする場合

    View Slide

  18. REST か RPC か
    ● RPCは良く言えば自由、悪く言えばバラバラなものができあがりがち
    ● したがって多くの場合でRESTを選択するべき
    ● ネットを見ると「 REST は考えることが多くてだるいよねー、RPC 最高!」という意
    見が散見されるが、そう発言する人のほとんどは、REST の「制約によるコンセン
    サス」というメリットを無視しているので注意

    View Slide

  19. 第二章
    リソース取得/更新の具体例

    View Slide

  20. エントリリソースの取得
    GET /api/1.0.0/users/20000
    HTTP/1.1 200 OK
    {
    “id”: 100,
    “nickname: ”nekokak”
    }
    単一のリソースのことを一般的に
    エントリリソースと呼ぶ
    最後の数字が user id 。
    URI がリソースを表現する
    一意のものになっているのがポイント

    View Slide

  21. コレクションリソースの取得
    GET /api/1.0.0/users
    {
    items: [
    { “id”: 100, “nickname: ”nekokak”},
    { “id”: 100, “nickname: ”zigorou”},
    ],
    nextCursor: “1000.1476255989”
    }
    複数リソースのことを一般的に
    コレクションリソース と呼ぶ
    次のページへのポインタ
    (ページネーション情報)

    View Slide

  22. リソースの新規作成
    POST /api/1.0.0/users
    HTTP/1.1 201 Created
    {
    “id”: 100,
    “nickname: ”zigorou”
    }
    (コレクションリソースに対して)
    新規のエントリリソースを追加、
    という操作なので、主体は users 。
    200 OK ではないことに注意
    作成されたリソースを返すのが
    一般的

    View Slide

  23. エントリリソースの削除
    DELETE /api/1.0.0/users/20000
    HTTP/1.1 204 No Content
    200 OK で返す人が多いが、
    レスポンスボディが無いことを
    明示的にするために、
    204 No Content がベター
    ※ No Content にするケースが多
    いというだけで、Content を返して
    はいけないというわけではない

    View Slide

  24. リソースの置き換え
    PUT /api/1.0.0/users/100
    Content-Type: application/json;
    {
    “id”: 100,
    “nickname: ”zigorou”
    }
    HTTP/1.1 204 No Conetnt
    PUT は差分更新でなく
    まるっと置き換える操作
    であることに注意。
    差分更新は PATCH 。

    View Slide

  25. リソースの差分更新
    PATCH /api/1.0.0/users/100
    Content-Type: application/json;
    [
    { “op”: “replace”, “path”: “/nickname”, “value”: “ねこかく” },
    { “op”: “add”, “path”: “/hobby”, “value”: “犬を飼う” }
    }
    HTTP/1.1 200 OK
    {
    “id”: 100,
    “nickname”: “ねこかく”,
    “hobby”: “犬を飼う”
    }
    JSON Patch という表現方法。
    ・nickname を「ねこかく」に変える
    ・hobby というフィールドを追加
    という差分更新をしている。

    View Slide

  26. JSON PATCH
    PATCH /api/1.0.0/users/100
    Content-Type: application/json;
    [
    { “op”: “replace”, “path”: “/nickname”, “value”: “ねこかく” },
    { “op”: “add”, “path”: “/hobby”, “value”: “犬を飼う” }
    }
    ⇒ https://tools.ietf.org/html/rfc6902
    op: operation の略。操作を指定。
    add, remove, replace, move
    などの語彙が使える。
    path: 更新するフィールドを
    JSON Pointer という
    表現方法で指定。
    value: 更新後の値を指定。

    View Slide

  27. 第三章
    Web API Bad Practice
    ※ ここから先は、ROA や REST に限らず
      Web API 全般の話になります。

    View Slide

  28. 悪い例: レスポンスがフラットな配列
    ● 複数リソースをフラットな配列で返す
    ○ 後からページネーション情報とか入れたくなったとき
    に詰むので、原則 items: みたいなエンベロープで
    包む
    ○ ページネーション情報などはレスポンスヘッダに含
    め、レスポンスボディはリソースだけを返すべきだ、
    という宗派もある
    ■ 気持ちは分かるけど、ぶっちゃけ使いづらいと
    思う
    GET /api/1.0.0/users
    HTTP/1.1 200 OK
    [
    { “id”: 100, … },
    { “id”: 200, … },
    ]

    View Slide

  29. 悪い例: /list みたいな URI
    ● GET /api/1.0.0/users/list みたいな URI
    ○ ROA において URI は「リソースの場所を表現するもの」であることを思い出す
    ○ users が user リソースの集合の意なので、list は蛇足

    View Slide

  30. 悪い例: リソースのフィールドをパスに含める
    ● ニックネームの変更で
    PUT /api/1.0.0/users/nickname?value=nekokak みたいなの
    ○ nickname というリソースがあるなら問題ないが、
    nickname が user というリソースの単なるフィールドである場合
    「 URI はリソースを指し示すもの」という原則から外れる
    ○ PUT /api/1.0.0/users ないしは
    PATCH /api/1.0.0/users で 良い
    (パラメータで更新フィールドを指定する)

    View Slide

  31. 悪い例: 動作を URI に含める
    ● POST /api/1.0.0/notifications/send みたいな URI
    ○ くどいが、URI はリソースの場所を表現するもの。
    send は行為であってリソースの表現ではない
    ○ send する == notification リソースの新規作成する行為
    なので、send は不要。
    POST /api/1.0.0/notifications で良い。

    View Slide

  32. 悪い例: 動作を URI に含める (2)
    ● GET /api/1.0.0/search_users みたいな URI
    ○ search は行為であってリソースの表現ではない
    ○ /api/1.0.0/users でクエリパラメータで
    絞り込み条件を指定できれば OK

    View Slide

  33. 悪い例: Limit-Offset のページネーション
    ● ページングが limit-offset 形式になっている
    ○ この方法が悪い理由 (1)
    ■ たとえば最初の 20 件を取得してから
    次の 20 件を取得するまでの間に
    データの追加/削除があった場合、
    実際に取得したい情報と取得した情報にズレが生じる

    View Slide

  34. 悪い例: Limit-Offset のページネーション
    ● ページングが limit-offset 形式になっている
    ○ この方法が悪い理由 (2)
    ■ MySQL などの RDBMS では
    limit 5 offset 10,000 というクエリを発行した場合、
    「 10,005 を取得して最初の 10,000 を捨てる」
    という処理が行われる
    ■ つまりページが後ろになるほどスロークエリになっていく

    View Slide

  35. 悪い例: Limit-Offset のページネーション
    ● ページングが limit-offset 形式になっている
    ○ 代わりにどうすべきか
    ○ 「先頭から数えて何件目」という Pagenation 情報でなく、
    「この ID より後のもの」や「この時刻より古いもの」
    というページネーション情報を提供するのが吉
    ■ id や created_at にインデックスが貼られている限り、
    クエリは高速
    ○ カーソル方式とか呼ばれたりします

    View Slide

  36. 悪い例: Limit-Offset のページネーション
    ● ページングが limit-offset 形式になっている
    ○ どうしても使いたいときは、
    指定できるページ数を制限すべきでしょう。

    View Slide

  37. ● 要求に失敗したなら
    レスポンスは 4XX か 5XX を返すべき
    POST /api/1.0.0/users
    HTTP/1.1 200 OK
    {
    “success”: false
    }
    悪い例: 要求に失敗してるのに 2XX を返す

    View Slide

  38. 悪い例: パスの不要なネスト
    ● ユーザー ID はアプリを横断してユニークなのに
    /apps/{app_id}/users/{user_id} みたいな URI
    ○ /users/{user_id} で良い

    View Slide

  39. 第四章
    補足

    View Slide

  40. 非同期処理時のレスポンスについて
    ● POST/PUT/DELETE などによるリソースの更新処理を
    非同期で行う場合は、
    202 Accepted というステータスコードが
    用意されているのでそれを用いる
    ○ 201 Created とか 200 OK とか返しちゃ駄目

    View Slide

  41. 排他制御処理を行ないたい場合
    ● レスポンスヘッダの Etag や Last-Modified を用いた
    Conditional Request (条件付きリクエスト)
    という手法を用いるのが一般的。(⇒ RFC7232 )
    ○ いわゆる楽観ロック相当の排他制御をすることが可能
    ○ 話すと長いのでここでの説明は割愛
    ○ 「 Conditional Request 」「条件付きリクエスト」等で
    各自ぐぐってくだださい。

    View Slide

  42. Web アプリケーションでも ROA をやりたい
    ● 知ってのとおり、Web のフォームは GET, POST しかサポートしてない
    ● オーバーロード POST という手法を用いて
    HTTP Method をオーバーライドする方法がある
    ● POST /books/{book_id}/delete みたいなのを
    キモいと感じる人は導入すべきでしょう

    View Slide

  43. Web アプリケーションでも ROA をやりたい
    ● オーバーロード POST の例
    ○ Rails ではフォームの _method パラメータに指定された値に HTTP Method
    が上書きされる
    ○ Perl の Catalyst では x-tunneled-method パラメータに指定された値に
    HTTP Method が上書きされる

    View Slide

  44. 参考

    View Slide

  45. ● Web API The Good Parts
    https://www.amazon.co.jp/dp/4873116864/
    ● Web を支える技術
    https://www.amazon.co.jp/dp/4774142042/
    参考文献

    View Slide

  46. END

    View Slide