Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RESTful API の設計のキホン

7780855696872b0b0eb8643ea7726512?s=47 Cside
October 12, 2016

RESTful API の設計のキホン

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

7780855696872b0b0eb8643ea7726512?s=128

Cside

October 12, 2016
Tweet

Transcript

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

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

    ◦ そして、誰でも設計できるようになってほしい(重要)
  3. おことわり • API の設計に正解はなく、これから話す指針はあくまで「私個人の考える Good Practice 」であることをご了承下さい ◦ マウンティングやめてね …

    。 ◦ なるべく、他にどういう流派があるかはあわせて説明するようにします
  4. 第一章 REST と RPC

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

  6. REST スタイルの特徴 • REST スタイルの API 設計では ROA( Resource Oriented

    Architecture )という 手法が広く知られている • Web API を RESTful にする == ROA にそって設計する • なので ROA の特徴をしっかり理解するのが重要
  7. ROA の 4 つの特徴 • アドレス可能性 ◦ リソースがURIを通して表現できること • ステートレス性

    ◦ APIリクエストのためのHTTPリクエストがすべて分離・独立していること(前の リクエストに影響されたりしない) • 接続性 ◦ リソースは別のリソースとの関連を表すリンクを持ちうること • 統一インタフェース ◦ HTTP メソッドを用いてリソースを参照/更新すること ◦ 後述
  8. リソースとは • URI を持ったデータのこと

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

  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  } このオブジェクトが リソース。
  11. ROA の統一インターフェイス HTTP メソッドを用いてリソースを取得/更新する。 • GET: リソースの取得 • HEAD: リソースの取得。HTTP

    ヘッダのみを返す • POST: リソースの新規作成 • PUT: 既存リソースの置き換え • PATCH: 既存リソースの差分更新 • DELETE: 既存リソースの削除
  12. RPC スタイル • XML-RPC, JSON-RPC など • 一言で言うと、制約が少なく、中央集権型

  13. RPC スタイル • RPC エンドポイント、メソッド名、リクエストパラメータ だけあれば利用できる リクエスト例 POST /rpc-endpoint Content-Type:

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

    application/json { "jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5] } エンドポイントは 1 個しか ない場合が多い リソースの操作方法は HTTP メソッ ドでなくパラメータで指定 基本全部 POST
  15. RPC スタイル • エンドポイントが 1 つしかない場合が多い ◦ リソースごとにユニークな URI を割り当てる

    REST と比べると対照的
  16. REST か RPC か • REST ◦ 特徴 ▪ ルール(制約)によって自明なコンセンサスが生まれ、システムを異なる

    人が分担して設計したとしても、一定の一貫性を担保できる ◦ 向いているケース ▪ 複数のエンジニアが API の設計をする場合
  17. REST か RPC か • RPC ◦ 特徴 ▪ 柔軟性が高く、中央集権型

    ◦ 向いているケース ▪ 限定されたエンジニアが設計をする場合
  18. REST か RPC か • RPCは良く言えば自由、悪く言えばバラバラなものができあがりがち • したがって多くの場合でRESTを選択するべき • ネットを見ると「

    REST は考えることが多くてだるいよねー、RPC 最高!」という意 見が散見されるが、そう発言する人のほとんどは、REST の「制約によるコンセン サス」というメリットを無視しているので注意
  19. 第二章 リソース取得/更新の具体例

  20. エントリリソースの取得 GET /api/1.0.0/users/20000 HTTP/1.1 200 OK { “id”: 100, “nickname:

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

    ”nekokak”}, { “id”: 100, “nickname: ”zigorou”}, ], nextCursor: “1000.1476255989” } 複数リソースのことを一般的に コレクションリソース と呼ぶ 次のページへのポインタ (ページネーション情報)
  22. リソースの新規作成 POST /api/1.0.0/users HTTP/1.1 201 Created { “id”: 100, “nickname:

    ”zigorou” } (コレクションリソースに対して) 新規のエントリリソースを追加、 という操作なので、主体は users 。 200 OK ではないことに注意 作成されたリソースを返すのが 一般的
  23. エントリリソースの削除 DELETE /api/1.0.0/users/20000 HTTP/1.1 204 No Content 200 OK で返す人が多いが、

    レスポンスボディが無いことを 明示的にするために、 204 No Content がベター ※ No Content にするケースが多 いというだけで、Content を返して はいけないというわけではない
  24. リソースの置き換え PUT /api/1.0.0/users/100 Content-Type: application/json; { “id”: 100, “nickname: ”zigorou”

    } HTTP/1.1 204 No Conetnt PUT は差分更新でなく まるっと置き換える操作 であることに注意。 差分更新は PATCH 。
  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 というフィールドを追加 という差分更新をしている。
  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: 更新後の値を指定。
  27. 第三章 Web API Bad Practice ※ ここから先は、ROA や REST に限らず

      Web API 全般の話になります。
  28. 悪い例: レスポンスがフラットな配列 • 複数リソースをフラットな配列で返す ◦ 後からページネーション情報とか入れたくなったとき に詰むので、原則 items: みたいなエンベロープで 包む

    ◦ ページネーション情報などはレスポンスヘッダに含 め、レスポンスボディはリソースだけを返すべきだ、 という宗派もある ▪ 気持ちは分かるけど、ぶっちゃけ使いづらいと 思う GET /api/1.0.0/users HTTP/1.1 200 OK [ { “id”: 100, … }, { “id”: 200, … }, ]
  29. 悪い例: /list みたいな URI • GET /api/1.0.0/users/list みたいな URI ◦

    ROA において URI は「リソースの場所を表現するもの」であることを思い出す ◦ users が user リソースの集合の意なので、list は蛇足
  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 で 良い (パラメータで更新フィールドを指定する)
  31. 悪い例: 動作を URI に含める • POST /api/1.0.0/notifications/send みたいな URI ◦

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

    ◦ search は行為であってリソースの表現ではない ◦ /api/1.0.0/users でクエリパラメータで 絞り込み条件を指定できれば OK
  33. 悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ この方法が悪い理由 (1)

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

    ▪ MySQL などの RDBMS では limit 5 offset 10,000 というクエリを発行した場合、 「 10,005 を取得して最初の 10,000 を捨てる」 という処理が行われる ▪ つまりページが後ろになるほどスロークエリになっていく
  35. 悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ 代わりにどうすべきか ◦

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

  37. • 要求に失敗したなら レスポンスは 4XX か 5XX を返すべき POST /api/1.0.0/users HTTP/1.1

    200 OK { “success”: false } 悪い例: 要求に失敗してるのに 2XX を返す
  38. 悪い例: パスの不要なネスト • ユーザー ID はアプリを横断してユニークなのに /apps/{app_id}/users/{user_id} みたいな URI ◦

    /users/{user_id} で良い
  39. 第四章 補足

  40. 非同期処理時のレスポンスについて • POST/PUT/DELETE などによるリソースの更新処理を 非同期で行う場合は、 202 Accepted というステータスコードが 用意されているのでそれを用いる ◦

    201 Created とか 200 OK とか返しちゃ駄目
  41. 排他制御処理を行ないたい場合 • レスポンスヘッダの Etag や Last-Modified を用いた Conditional Request (条件付きリクエスト)

    という手法を用いるのが一般的。(⇒ RFC7232 ) ◦ いわゆる楽観ロック相当の排他制御をすることが可能 ◦ 話すと長いのでここでの説明は割愛 ◦ 「 Conditional Request 」「条件付きリクエスト」等で 各自ぐぐってくだださい。
  42. Web アプリケーションでも ROA をやりたい • 知ってのとおり、Web のフォームは GET, POST しかサポートしてない

    • オーバーロード POST という手法を用いて HTTP Method をオーバーライドする方法がある • POST /books/{book_id}/delete みたいなのを キモいと感じる人は導入すべきでしょう
  43. Web アプリケーションでも ROA をやりたい • オーバーロード POST の例 ◦ Rails

    ではフォームの _method パラメータに指定された値に HTTP Method が上書きされる ◦ Perl の Catalyst では x-tunneled-method パラメータに指定された値に HTTP Method が上書きされる
  44. 参考

  45. • Web API The Good Parts https://www.amazon.co.jp/dp/4873116864/ • Web を支える技術

    https://www.amazon.co.jp/dp/4774142042/ 参考文献
  46. END