RESTful API の設計のキホン

7780855696872b0b0eb8643ea7726512?s=47 Cside
October 12, 2016

RESTful API の設計のキホン

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

7780855696872b0b0eb8643ea7726512?s=128

Cside

October 12, 2016
Tweet

Transcript

  1. 6.

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

    Architecture )という 手法が広く知られている • Web API を RESTful にする == ROA にそって設計する • なので ROA の特徴をしっかり理解するのが重要
  2. 7.

    ROA の 4 つの特徴 • アドレス可能性 ◦ リソースがURIを通して表現できること • ステートレス性

    ◦ APIリクエストのためのHTTPリクエストがすべて分離・独立していること(前の リクエストに影響されたりしない) • 接続性 ◦ リソースは別のリソースとの関連を表すリンクを持ちうること • 統一インタフェース ◦ HTTP メソッドを用いてリソースを参照/更新すること ◦ 後述
  3. 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  } このオブジェクトが リソース。
  4. 11.

    ROA の統一インターフェイス HTTP メソッドを用いてリソースを取得/更新する。 • GET: リソースの取得 • HEAD: リソースの取得。HTTP

    ヘッダのみを返す • POST: リソースの新規作成 • PUT: 既存リソースの置き換え • PATCH: 既存リソースの差分更新 • DELETE: 既存リソースの削除
  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
  6. 16.

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

    人が分担して設計したとしても、一定の一貫性を担保できる ◦ 向いているケース ▪ 複数のエンジニアが API の設計をする場合
  7. 17.

    REST か RPC か • RPC ◦ 特徴 ▪ 柔軟性が高く、中央集権型

    ◦ 向いているケース ▪ 限定されたエンジニアが設計をする場合
  8. 18.

    REST か RPC か • RPCは良く言えば自由、悪く言えばバラバラなものができあがりがち • したがって多くの場合でRESTを選択するべき • ネットを見ると「

    REST は考えることが多くてだるいよねー、RPC 最高!」という意 見が散見されるが、そう発言する人のほとんどは、REST の「制約によるコンセン サス」というメリットを無視しているので注意
  9. 20.

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

    ”nekokak” } 単一のリソースのことを一般的に エントリリソースと呼ぶ 最後の数字が user id 。 URI がリソースを表現する 一意のものになっているのがポイント
  10. 21.

    コレクションリソースの取得 GET /api/1.0.0/users { items: [ { “id”: 100, “nickname:

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

    リソースの新規作成 POST /api/1.0.0/users HTTP/1.1 201 Created { “id”: 100, “nickname:

    ”zigorou” } (コレクションリソースに対して) 新規のエントリリソースを追加、 という操作なので、主体は users 。 200 OK ではないことに注意 作成されたリソースを返すのが 一般的
  12. 23.

    エントリリソースの削除 DELETE /api/1.0.0/users/20000 HTTP/1.1 204 No Content 200 OK で返す人が多いが、

    レスポンスボディが無いことを 明示的にするために、 204 No Content がベター ※ No Content にするケースが多 いというだけで、Content を返して はいけないというわけではない
  13. 24.

    リソースの置き換え PUT /api/1.0.0/users/100 Content-Type: application/json; { “id”: 100, “nickname: ”zigorou”

    } HTTP/1.1 204 No Conetnt PUT は差分更新でなく まるっと置き換える操作 であることに注意。 差分更新は PATCH 。
  14. 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 というフィールドを追加 という差分更新をしている。
  15. 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: 更新後の値を指定。
  16. 28.

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

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

    悪い例: /list みたいな URI • GET /api/1.0.0/users/list みたいな URI ◦

    ROA において URI は「リソースの場所を表現するもの」であることを思い出す ◦ users が user リソースの集合の意なので、list は蛇足
  18. 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 で 良い (パラメータで更新フィールドを指定する)
  19. 31.

    悪い例: 動作を URI に含める • POST /api/1.0.0/notifications/send みたいな URI ◦

    くどいが、URI はリソースの場所を表現するもの。 send は行為であってリソースの表現ではない ◦ send する == notification リソースの新規作成する行為 なので、send は不要。 POST /api/1.0.0/notifications で良い。
  20. 32.

    悪い例: 動作を URI に含める (2) • GET /api/1.0.0/search_users みたいな URI

    ◦ search は行為であってリソースの表現ではない ◦ /api/1.0.0/users でクエリパラメータで 絞り込み条件を指定できれば OK
  21. 33.

    悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ この方法が悪い理由 (1)

    ▪ たとえば最初の 20 件を取得してから 次の 20 件を取得するまでの間に データの追加/削除があった場合、 実際に取得したい情報と取得した情報にズレが生じる
  22. 34.

    悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ この方法が悪い理由 (2)

    ▪ MySQL などの RDBMS では limit 5 offset 10,000 というクエリを発行した場合、 「 10,005 を取得して最初の 10,000 を捨てる」 という処理が行われる ▪ つまりページが後ろになるほどスロークエリになっていく
  23. 35.

    悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ 代わりにどうすべきか ◦

    「先頭から数えて何件目」という Pagenation 情報でなく、 「この ID より後のもの」や「この時刻より古いもの」 というページネーション情報を提供するのが吉 ▪ id や created_at にインデックスが貼られている限り、 クエリは高速 ◦ カーソル方式とか呼ばれたりします
  24. 37.

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

    200 OK { “success”: false } 悪い例: 要求に失敗してるのに 2XX を返す
  25. 41.

    排他制御処理を行ないたい場合 • レスポンスヘッダの Etag や Last-Modified を用いた Conditional Request (条件付きリクエスト)

    という手法を用いるのが一般的。(⇒ RFC7232 ) ◦ いわゆる楽観ロック相当の排他制御をすることが可能 ◦ 話すと長いのでここでの説明は割愛 ◦ 「 Conditional Request 」「条件付きリクエスト」等で 各自ぐぐってくだださい。
  26. 42.

    Web アプリケーションでも ROA をやりたい • 知ってのとおり、Web のフォームは GET, POST しかサポートしてない

    • オーバーロード POST という手法を用いて HTTP Method をオーバーライドする方法がある • POST /books/{book_id}/delete みたいなのを キモいと感じる人は導入すべきでしょう
  27. 43.

    Web アプリケーションでも ROA をやりたい • オーバーロード POST の例 ◦ Rails

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

    END