Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
RESTful API の設計のキホン
Search
Cside
October 12, 2016
Programming
94
17k
RESTful API の設計のキホン
2016/10/12 社内勉強会で使ったスライドを社外向けに一部加筆訂正したもの
Cside
October 12, 2016
Tweet
Share
More Decks by Cside
See All by Cside
さくらVPS、DotCloudの次にくるRackhub
cside_
5
8.8k
iPhoneアプリ「Music Stream(仮)」紹介 ( #on_lab )
cside_
1
1.2k
Other Decks in Programming
See All in Programming
Generative AI Use Cases JP (略称:GenU)奮闘記
hideg
0
160
CSC509 Lecture 09
javiergs
PRO
0
110
のびしろを広げる巻き込まれ力:偶然を活かすキャリアの作り方/oso2024
takahashiikki
1
410
AWS IaCの注目アップデート 2024年10月版
konokenj
3
3.1k
Vitest Browser Mode への期待 / Vitest Browser Mode
odanado
PRO
2
1.7k
Vue3の一歩踏み込んだパフォーマンスチューニング2024
hal_spidernight
3
3.1k
詳細解説! ArrayListの仕組みと実装
yujisoftware
0
490
約9000個の自動テストの 時間を50分->10分に短縮 Flakyテストを1%以下に抑えた話
hatsu38
24
11k
Tuning GraphQL on Rails
pyama86
2
1.1k
cXML という電子商取引の トランザクションを支える プロトコルと向きあっている話
phigasui
3
2.3k
推し活としてのrails new/oshikatsu_ha_iizo
sakahukamaki
3
1.7k
Nuxtベースの「WXT」でChrome拡張を作成する | Vue Fes 2024 ランチセッション
moshi1121
1
530
Featured
See All Featured
The World Runs on Bad Software
bkeepers
PRO
65
11k
Fashionably flexible responsive web design (full day workshop)
malarkey
404
65k
Producing Creativity
orderedlist
PRO
341
39k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5k
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
Making Projects Easy
brettharned
115
5.9k
YesSQL, Process and Tooling at Scale
rocio
167
14k
StorybookのUI Testing Handbookを読んだ
zakiyama
26
5.2k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
159
15k
Measuring & Analyzing Core Web Vitals
bluesmoon
1
41
Building a Modern Day E-commerce SEO Strategy
aleyda
38
6.9k
Adopting Sorbet at Scale
ufuk
73
9k
Transcript
RESTful API の設計のキホン 2016/10/12 Hiroki Honda @Cside_
なぜこの会を開いたか • 自分の関わった某案件で、API の新エンドポイントのインターフェイスをレビューす るときに「こういう風に直して下さい」と指示はしてきたけれど、「なぜそのように直さ なければならないのか」までちゃんと説明しきれてなかった ◦ 納得できてない人もいると思う ◦ なのでちゃんと説明したい
◦ そして、誰でも設計できるようになってほしい(重要)
おことわり • API の設計に正解はなく、これから話す指針はあくまで「私個人の考える Good Practice 」であることをご了承下さい ◦ マウンティングやめてね …
。 ◦ なるべく、他にどういう流派があるかはあわせて説明するようにします
第一章 REST と RPC
Web API のスタイル • REST スタイル • RPC スタイル が主なもの。
REST スタイルの特徴 • REST スタイルの API 設計では ROA( Resource Oriented
Architecture )という 手法が広く知られている • Web API を RESTful にする == ROA にそって設計する • なので ROA の特徴をしっかり理解するのが重要
ROA の 4 つの特徴 • アドレス可能性 ◦ リソースがURIを通して表現できること • ステートレス性
◦ APIリクエストのためのHTTPリクエストがすべて分離・独立していること(前の リクエストに影響されたりしない) • 接続性 ◦ リソースは別のリソースとの関連を表すリンクを持ちうること • 統一インタフェース ◦ HTTP メソッドを用いてリソースを参照/更新すること ◦ 後述
リソースとは • URI を持ったデータのこと
リソースの例 • 例えば、モバゲーにおける僕のプロフィールというリソースは以下の URI を持って いる ◦ /api/restful/v1/people/32592054/@self
リソースの例 • 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 } このオブジェクトが リソース。
ROA の統一インターフェイス HTTP メソッドを用いてリソースを取得/更新する。 • GET: リソースの取得 • HEAD: リソースの取得。HTTP
ヘッダのみを返す • POST: リソースの新規作成 • PUT: 既存リソースの置き換え • PATCH: 既存リソースの差分更新 • DELETE: 既存リソースの削除
RPC スタイル • XML-RPC, JSON-RPC など • 一言で言うと、制約が少なく、中央集権型
RPC スタイル • RPC エンドポイント、メソッド名、リクエストパラメータ だけあれば利用できる リクエスト例 POST /rpc-endpoint Content-Type:
application/json { "jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5] }
RPC スタイル • RPC エンドポイント、メソッド名、リクエストパラメータ だけあれば利用できる リクエスト例 POST /rpc-endpoint Content-Type:
application/json { "jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5] } エンドポイントは 1 個しか ない場合が多い リソースの操作方法は HTTP メソッ ドでなくパラメータで指定 基本全部 POST
RPC スタイル • エンドポイントが 1 つしかない場合が多い ◦ リソースごとにユニークな URI を割り当てる
REST と比べると対照的
REST か RPC か • REST ◦ 特徴 ▪ ルール(制約)によって自明なコンセンサスが生まれ、システムを異なる
人が分担して設計したとしても、一定の一貫性を担保できる ◦ 向いているケース ▪ 複数のエンジニアが API の設計をする場合
REST か RPC か • RPC ◦ 特徴 ▪ 柔軟性が高く、中央集権型
◦ 向いているケース ▪ 限定されたエンジニアが設計をする場合
REST か RPC か • RPCは良く言えば自由、悪く言えばバラバラなものができあがりがち • したがって多くの場合でRESTを選択するべき • ネットを見ると「
REST は考えることが多くてだるいよねー、RPC 最高!」という意 見が散見されるが、そう発言する人のほとんどは、REST の「制約によるコンセン サス」というメリットを無視しているので注意
第二章 リソース取得/更新の具体例
エントリリソースの取得 GET /api/1.0.0/users/20000 HTTP/1.1 200 OK { “id”: 100, “nickname:
”nekokak” } 単一のリソースのことを一般的に エントリリソースと呼ぶ 最後の数字が user id 。 URI がリソースを表現する 一意のものになっているのがポイント
コレクションリソースの取得 GET /api/1.0.0/users { items: [ { “id”: 100, “nickname:
”nekokak”}, { “id”: 100, “nickname: ”zigorou”}, ], nextCursor: “1000.1476255989” } 複数リソースのことを一般的に コレクションリソース と呼ぶ 次のページへのポインタ (ページネーション情報)
リソースの新規作成 POST /api/1.0.0/users HTTP/1.1 201 Created { “id”: 100, “nickname:
”zigorou” } (コレクションリソースに対して) 新規のエントリリソースを追加、 という操作なので、主体は users 。 200 OK ではないことに注意 作成されたリソースを返すのが 一般的
エントリリソースの削除 DELETE /api/1.0.0/users/20000 HTTP/1.1 204 No Content 200 OK で返す人が多いが、
レスポンスボディが無いことを 明示的にするために、 204 No Content がベター ※ No Content にするケースが多 いというだけで、Content を返して はいけないというわけではない
リソースの置き換え PUT /api/1.0.0/users/100 Content-Type: application/json; { “id”: 100, “nickname: ”zigorou”
} HTTP/1.1 204 No Conetnt PUT は差分更新でなく まるっと置き換える操作 であることに注意。 差分更新は PATCH 。
リソースの差分更新 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 というフィールドを追加 という差分更新をしている。
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: 更新後の値を指定。
第三章 Web API Bad Practice ※ ここから先は、ROA や REST に限らず
Web API 全般の話になります。
悪い例: レスポンスがフラットな配列 • 複数リソースをフラットな配列で返す ◦ 後からページネーション情報とか入れたくなったとき に詰むので、原則 items: みたいなエンベロープで 包む
◦ ページネーション情報などはレスポンスヘッダに含 め、レスポンスボディはリソースだけを返すべきだ、 という宗派もある ▪ 気持ちは分かるけど、ぶっちゃけ使いづらいと 思う GET /api/1.0.0/users HTTP/1.1 200 OK [ { “id”: 100, … }, { “id”: 200, … }, ]
悪い例: /list みたいな URI • GET /api/1.0.0/users/list みたいな URI ◦
ROA において URI は「リソースの場所を表現するもの」であることを思い出す ◦ users が user リソースの集合の意なので、list は蛇足
悪い例: リソースのフィールドをパスに含める • ニックネームの変更で 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 で 良い (パラメータで更新フィールドを指定する)
悪い例: 動作を URI に含める • POST /api/1.0.0/notifications/send みたいな URI ◦
くどいが、URI はリソースの場所を表現するもの。 send は行為であってリソースの表現ではない ◦ send する == notification リソースの新規作成する行為 なので、send は不要。 POST /api/1.0.0/notifications で良い。
悪い例: 動作を URI に含める (2) • GET /api/1.0.0/search_users みたいな URI
◦ search は行為であってリソースの表現ではない ◦ /api/1.0.0/users でクエリパラメータで 絞り込み条件を指定できれば OK
悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ この方法が悪い理由 (1)
▪ たとえば最初の 20 件を取得してから 次の 20 件を取得するまでの間に データの追加/削除があった場合、 実際に取得したい情報と取得した情報にズレが生じる
悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ この方法が悪い理由 (2)
▪ MySQL などの RDBMS では limit 5 offset 10,000 というクエリを発行した場合、 「 10,005 を取得して最初の 10,000 を捨てる」 という処理が行われる ▪ つまりページが後ろになるほどスロークエリになっていく
悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ 代わりにどうすべきか ◦
「先頭から数えて何件目」という Pagenation 情報でなく、 「この ID より後のもの」や「この時刻より古いもの」 というページネーション情報を提供するのが吉 ▪ id や created_at にインデックスが貼られている限り、 クエリは高速 ◦ カーソル方式とか呼ばれたりします
悪い例: Limit-Offset のページネーション • ページングが limit-offset 形式になっている ◦ どうしても使いたいときは、 指定できるページ数を制限すべきでしょう。
• 要求に失敗したなら レスポンスは 4XX か 5XX を返すべき POST /api/1.0.0/users HTTP/1.1
200 OK { “success”: false } 悪い例: 要求に失敗してるのに 2XX を返す
悪い例: パスの不要なネスト • ユーザー ID はアプリを横断してユニークなのに /apps/{app_id}/users/{user_id} みたいな URI ◦
/users/{user_id} で良い
第四章 補足
非同期処理時のレスポンスについて • POST/PUT/DELETE などによるリソースの更新処理を 非同期で行う場合は、 202 Accepted というステータスコードが 用意されているのでそれを用いる ◦
201 Created とか 200 OK とか返しちゃ駄目
排他制御処理を行ないたい場合 • レスポンスヘッダの Etag や Last-Modified を用いた Conditional Request (条件付きリクエスト)
という手法を用いるのが一般的。(⇒ RFC7232 ) ◦ いわゆる楽観ロック相当の排他制御をすることが可能 ◦ 話すと長いのでここでの説明は割愛 ◦ 「 Conditional Request 」「条件付きリクエスト」等で 各自ぐぐってくだださい。
Web アプリケーションでも ROA をやりたい • 知ってのとおり、Web のフォームは GET, POST しかサポートしてない
• オーバーロード POST という手法を用いて HTTP Method をオーバーライドする方法がある • POST /books/{book_id}/delete みたいなのを キモいと感じる人は導入すべきでしょう
Web アプリケーションでも ROA をやりたい • オーバーロード POST の例 ◦ Rails
ではフォームの _method パラメータに指定された値に HTTP Method が上書きされる ◦ Perl の Catalyst では x-tunneled-method パラメータに指定された値に HTTP Method が上書きされる
参考
• Web API The Good Parts https://www.amazon.co.jp/dp/4873116864/ • Web を支える技術
https://www.amazon.co.jp/dp/4774142042/ 参考文献
END