Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

第一章 REST と RPC

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

リソースの例 ● 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  } このオブジェクトが リソース。

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

リソースの差分更新 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 というフィールドを追加 という差分更新をしている。

Slide 26

Slide 26 text

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: 更新後の値を指定。

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

悪い例: リソースのフィールドをパスに含める ● ニックネームの変更で 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 で 良い (パラメータで更新フィールドを指定する)

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

第四章 補足

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

参考

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

END