この記事では、シングルページアプリケーション開発での Web API 設計について書いていきます。
ここで言う「エンドポイント」とは、HTTP メソッドと URL の組み合わせです。また、本記事で扱うのは、いわゆる REST API と呼ばれるタイプの Web API です。最近は GraphQL が台頭してきていますが、まだ現場では REST タイプの API を扱うことがほとんどでしょう。
API 設計は大きく2つの側面があります。エンドポイント定義と、リクエストおよびレスポンスメッセージの JSON 定義です。本記事では、特にエンドポイント定義の設計について取りあげます。なぜなら、どちらかというと、エンドポイント定義のほうが、これから SPA 開発にチャレンジする方にとって、難しさがあるように感じるからです。
Web API とは
何を API にするのか
まず、そもそも何を API にすればよいのか、というお話から始めます。
Web API とは、リソースサーバがフロントエンドを通してユーザーに公開する機能群です。
つまり、1機能が1つの API になります。
反対に、以下の図のように、1画面を構成するデータをまとめて返却する API を、画面ごとに作ってしまう、または1つのボタンアクションから呼び出す API を、ボタンごとに作ってしまう設計モデルは、好ましくない例です。
API は、シンプルな機能ごとに作られてこそ威力を発揮します。ある画面を構成するために呼び出す必要な API が複数あっても OK ですし、同じ機能のボタンであれば、同じ API を呼び出せばよいのです。
では、「機能」とは何でしょうか?
たとえば、各プロジェクトで「機能一覧」のようなドキュメントを作ることが多いと思いますが、当然プロジェクトごとに記載粒度は異なるはずですので、これは基準になりません。
また、テーブルごとに(機械的に)CRUD を切り出せばいいのかというと、結果的にそうなることもあるかもしれませんが、本格的なアプリケーションはそんなに単純な機能ばかりではないでしょう。
後述する、「リソース(何を)」と「メソッド(どうする)」で表現できる単位、と理解できればよいかと思います。
いつ API を設計しはじめるか
次に、API の設計が、全体の設計のうちどのタイミングに位置するかをお話しします。
結論から言うと、UI(画面)がある程度固まって、データベース定義の初期段階、ER 図ができたあたりから API を設計し始めることをお勧めします。
そもそも SPA には以下の図のような設計ポイントがあります。つまり、UI 設計、API 設計、データベース設計と、フロントエンド・サーバサイドそれぞれのビジネスロジックです。
個人的な意見としては、設計の観点で特に重要なのが UI / API / DB の設計です。それぞれを繋ぐビジネスロジックについては、アイディアの共有という意味で文章や図にするのはいいことだと思いますが、詳細に設計すればするほどプログラムコードに近くなるので、正式なドキュメントとしての価値はコードで担保できると考えています。
さて、話を戻すと、設計の順番として UI → DB モデリング → API がオススメだと言っても、そもそも DB が UI のために存在したり、API が DB のために存在したりするわけではありません。あくまでそれぞれが、ユーザーの体験や与えたい、感じてほしい価値に従うべきです。
ただ、実際の案件では、UI からユーザーの体験を設計し始めることが多いでしょう。モック的に作成した UI から着想を得て、もっとこうしたらいいのでは、などのアイディアが出てきて、だんだんと作りたいアプリが具体的に見えてきます。
アプリが実現するべき機能が具体化してくると、必要なデータおよびその構造を考え始めることができます。全体のデータモデリングが、サーバサイドが持つリソース(リソースについては後述しますが、テーブル=リソースではありません)の骨格となるので、そこから API の設計に入るのがちょうどいいと思います。
UI / API / DB が相互に交換可能に設計できているのが理想でしょう。たとえば UI が Web からモバイルアプリに替わっても、または DB がオンプレの RDB から Firebase のようなサービスに替わっても、その他の設計は変更しなくて OK というように。
ただし理想は理想であって、設計はバランスです。ある程度、UI の都合に合わせて API を設計することもあっていいと思います。
少し話が広がってしまいましたが、ここから API のエンドポイントについて考えていきます。
エンドポイントの基本構造
まずはエンドポイントの基本構造についてのお話です。
以下は、HTTP リクエストの例です。
GET /articles HTTP/1.1
Host: foo.bar.com
Accept: application/json
このうち、エンドポイント設計において考えるのは、GET /articles
の部分、つまり HTTP メソッドとリクエスト対象(URL)の組み合わせです。
リソースとメソッド
Web API では、URL 部分を「リソース」と見なし、HTTP メソッドと URL の組み合わせを、
「なにを(= リソース)」「どうする(= HTTP メソッド)」
という形式で捉えます。
GET /products
ここでのリソースは products
(商品)で、メソッドは GET
(取得)です。つまり、「商品一覧を取得する」という意味です。
リソース
リソースとは、URL で表される、API によって操作される対象のことです。
なんだか抽象的な言い方になってしまいましたが、直接的な説明や置き換えが難しい概念です。たとえばデータベースのテーブルすなわちリソースかと言うと、もちろんそうなる場合もありますが、厳密にイコールである必要はありません。
HTTP メソッドと組み合わせて、うまく機能を表現するように作っていきます。
具体的な例は後述します。
HTTP メソッド
HTTP メソッドには以下の種類があります。正確にはあといくつかありますが、設計において扱うのは以下の5種類です。
メソッド | 意味 |
---|---|
GET | リソースの取得 |
POST | リソースの追加 |
PATCH | リソースの一部更新 |
PUT | リソースの置き換え |
DELETE | リソースの削除 |
旧来の(SPAではない)マルチページアプリではメソッドが GET と POST しか使えませんでした。なぜなら、HTML のフォーム送信が GET と POST にしか対応できていないためです。
2種類しかなく表現力に乏しかったため、たとえば、以下のように、「どうする」の部分の一部を URL が担っていました。
POST /products/create
POST /products/update
HTML フォームを介さない非同期通信(つまり Web API)では、このような制約はなく、HTTP に定義されたメソッドをフル活用できるので、以下のようなエンドポイントを表現することが可能となりました。
POST /products
PATCH /products
「どうする」の表現を HTTP メソッドに任せられるようになったからこそ、「何を」に専念する「リソース」という設計上の概念も可能となったと言えるでしょう。
階層構造
もう一つ、基本として押さえてほしいのは、URL は階層構造で表現されるという点です。
これは Web API に限らず、「普通の」Web サイトにも当てはまる、普遍的な特徴です。たとえば、以下の例を見てください。ありそうな企業ホームページの URL を考えてみました。
URL | ページ |
---|---|
/ |
トップページ |
/services |
事業一覧 |
/services/web-development |
Web 開発事業紹介 |
/services/marketing |
マーケティング事業紹介 |
/recruit |
採用トップ |
/recruit/jobs |
採用職種一覧 |
/recruit/jobs/designer |
デザイナー募集要項 |
トップページの子階層として事業一覧ページがあり、事業一覧ページのさらに子階層に各事業の紹介ページがある、といったように、URL は親子関係やツリーで表されるような階層構造で定義されます。
階層構造は、Web API においてリソースを表現する際にも重要な考え方になります。
リソースの表現
ここからは、より具体的な設計のポイントに話を進めます。
まずは「リソース」の表現方法についてです。
階層構造を利用したリソース表現
先ほども述べた通り、リソースも URL の特徴に倣って階層構造で表現しましょう。
以下の例において、123
の部分は本を一意に特定する ID だと思ってください。
URL | 意味 |
---|---|
/books |
蔵書一覧 |
/books/123 |
特定の一冊 |
/books/123/authors |
ある本の著者 |
/books/123/reviews |
ある本のレビュー |
このように、操作の対象=リソースも階層構造を用いて表現します。HTTP メソッドと組み合わせると、たとえば以下のようなエンドポイントができあがります。
POST /books/123/reviews
もうひとつ階層化の例です。
URL | 意味 |
---|---|
/bands |
バンド一覧 |
/bands/the-beatles |
ビートルズ |
/bands/the-beatles/albums |
ビートルズのアルバム |
/bands/the-beatles/albums/abbey-road |
『アビィ・ロード』 |
/bands/the-beatles/albums/abbey-road/songs |
『アビィ・ロード』収録曲 |
/bands/the-beatles/albums/abbey-road/songs/something |
『サムシング』 |
階層が増えて、多少長く感じても問題はありません。そのような感覚にはほとんど根拠がないでしょうし、階層が崩れて意味が(一部の開発者にしか)分からなくなってしまうことのほうが問題です。
コトをリソースとして表現する
「モノ」だけでなく、「コト」もリソースとして表現しなければなりません。
「商品」「コメント」「書籍」など、明らかにモノであるリソースは考えやすいでしょう。機能としては、「商品を登録する」「コメントを編集する」などですね。しかし、「商品をお気に入りに入れる」「コメントにいいねする」「本を貸し出す」などはどうでしょうか?
たとえば、コメントにいいねする場合のリソースは以下のように考えられます。
/comments/コメントID/like
「いいね」をリソースとして捉えるわけです。HTTP メソッドと組み合わせると、以下のようなエンドポイントを作れます。
PUT /comments/コメントID/like
DELETE /comments/コメントID/like
以下は、SNS アプリで、あるユーザーをフォローする例です。
/users/相手のユーザー名/follow
ただ、これはより細かく階層を設けて、以下のように表現することもできるでしょう。
/users/相手のユーザー名/followers/自分のユーザー名
パラメータの受け渡し
ここからは、個別の論点について取り上げます。
まずはパラメータの受け渡し方法についてです。主にリソースに条件付けをして、「たくさんあるうちのどれか」を指定するパラメータ(変数)をサーバに渡す方法は、HTTP メソッドを問わずに言うと、一般的に「クエリパラメータ」「パスパラメータ」「リクエストボディ」が考えられます。
クエリパラメータ
これは GET
の場合のみですが、URL に ?
に続くパラメータが与えられるパターンです。
/products?category=12&color=34&size=m
検索機能での用例を目にしたことがあると思います。
パスパラメータ
パスの一部にパラメータを含めるパターンです。
/products/123
大抵、リソースを一意に特定する ID などの値が指定されます。
リクエストボディ
もうひとつ、これはエンドポイントではありませんが、リクエストボディにリソースを特定できる ID を含める方法も、論理的には考えられます。
PATCH /users
{"id": "123", "email": "foo@bar.com"}
クエリパラメータの反対に、これは POST
PATCH
PUT
DELETE
の場合ですね。
クエリとパスの使い分け
クエリパラメータとパスパラメータの使い分けについては、以下の基準で OK でしょう。
- 任意のパラメータにはクエリパラメータ
- 必須のパラメータにはパスパラメータ
ざっくり言い換えると、
- クエリパラメータは検索系
- パスパラメータは個別のリソースを特定
という解釈でも OK かもしれません。
そのパラメータがなくてもリソースとして成り立たつ、意味が変わらないのがクエリパラメータ、それがないとリソースとしての意味が変わってしまうのがパスパラメータ、という表現もできます。
ただし、どちらつかないような場合も存在します。たとえば以下の例を見てください。あるユーザーのツイート一覧を取得するエンドポイントです。
GET /tweets?user=john123
GET /users/john123/tweets
tweets
を起点にすれば、ユーザーを検索条件と見なしてクエリパラメータとして表現できそうですし、users
を起点にすれば階層構造を利用してパスパラメータとしても表現できます。
どちらがいいかは要件によるでしょう。その API の使いどころがそれこそツイートの検索機能であればクエリパラメータが良さそうですし、他の条件がありえない、個別のユーザー別ツイートページであれば、パスに含めるのが良いでしょう。
この例のように、形式だけではどちらが好ましいか判断できないパターンもあります。そこは要件と相談しながら、設計判断しましょう。
リクエストボディとパスの使い分け
大抵の場合で、パスを選択するのが好ましいです。
なぜなら、リクエストボディに ID などを含めてしまうと、URL をリソースとして見ることができなくなる、意味をなさなくなるからです。
POST? PATCH? PUT?
ここでは、POST
PATCH
PUT
の使い分けについて見ていきます。
POST
(追加)と PATCH
(編集)は使い分けにあまり迷うところはないと思うので、それぞれと PUT
(置換)の使い分けを取り上げます。
PUT とは
「置換」とは、「なかったら作る、あれば上書きする」という意味です。
たとえば、AWS の S3 を例として考えてみてください。AWS が公開する API では、S3 バケットに対するファイルの格納は、PUT
で定義されています。foo.png
という名前のファイルを PUT
すると、バケット内にその名前のファイルが出来上がります。
では、もう一度同名のファイルを PUT
するとどうなるでしょうか?もうひとつファイルができるのではなく、既存の foo.png
が上書きされます。
このように、PUT
の場合は何度呼び出しても対象は2つ以上に増えません。
POST と PUT
POST
と PUT
については、複数回実行したとき、リソースが増えるのか、増えないのかで使い分けるとよいでしょう。
たとえば、先に例に挙げた「いいね」の例で考えます。普通ありそうな仕様だと、ある対象に対しての「いいね」は1人1回まででしょう。その場合は PUT
が良さそうです。
PUT /tweets/9876543/like
しかし、特殊な仕様で、1人がある対象に対して何度も「いいね」できてカウントアップしていく場合は、POST
が適切になります。
POST /tweets/9876543/like
PATCH と PUT
次に PATCH
と PUT
の使い分けです。
一部更新である PATCH
のエンドポイントは、PUT
でよりシンプルになる可能性があります。
たとえば、ユーザーの設定画面(マイページみたいなイメージ)で、配信メールを受け取るかどうかの設定を変更するエンドポイントを考えてみましょう。
PATCH /settings
{
"receiveEmail": true
}
設定(settings
)リソースの一部を更新するので、上記でも間違ってはいないのですが、PATCH
は実装上、少しだけ厄介と言えます。
と言うのも、たとえば receiveEmail
以外の項目が渡ってきたときはどうするのでしょう?存在しない項目や、ユーザー側でいじってほしくない項目だった場合は?
包括的な編集機能は必要なく、「配信メールの設定変更」という機能だけあれば OK なのであれば、PUT
を使って以下のようにも表現できるでしょう。
PUT /settings/receive-email
ちなみに配信メールを OFF にする場合は DELETE
を使って表現できます。
DELETE /settings/receive-email
包括的な編集機能が必要な場合であっても、PUT
は一考の価値があります。
というのも、その場合はおそらく入力欄が並んだ編集画面があるのでしょう。PATCH
の場合は「一部更新」なので、更新すべきデータのみ渡すほうが意味的に好ましそうです。
ここで実装を想像してみてください。複数の入力欄から、更新があった項目のみ選別して API に渡すのは少し面倒ですよね。だったら細かいこと言わずにすべて渡せばいいじゃないか、という判断もアリですが、それなら意味的に PUT
= 置換のほうが近いのではないでしょうか。
いずれにせよ、追加・更新系の機能に出くわしたら、これは POST
でやるとどうなるか?PATCH
はありえるか?PUT
だとどうか?と考え、色々なパターンを探るのがよいです。設計とは選択肢から選び取る活動なので、選択肢を出してみるのがまずは重要です。
DELETE
最後に DELETE
についてです。
今まで何度か例に挙げていますが、DELETE
はリソースそのものを削除する場合だけでなく、何かの設定などを OFF する場合にも使えます。
PUT /tweets/9876543/unlike
DELETE /tweets/9876543/like
ON が PUT
で OFF が DELETE
といった対で表現するパターンを覚えておくと便利です。
アンチパターン
たまに見かけるアンチパターンを紹介します。
無意味なリソース
GET /products/search?www=xxx&yyy=zzz
GET /products/123/detail
上の例において、search
や detail
は不要です。
パスの一部が無くても意味が通じるかどうか、見直して考えてみましょう。
階層構造が不正
GET /users/profile/1234
なぜか変数を後ろに寄せたがる人がいますが、そういう問題ではないです。
階層構造に忠実に。
GET /users/1234/profile
その他の論点
その他、ちょっとした形式上の論点を紹介します。
大文字か? 小文字か?
まずは URL に大文字を許可するか?という論点です。
/foo-bar
/foo_bar
/fooBar
個人的には、URL はすべて小文字のスネークケースまたはケバブケースで統一します。
大文字を含めると、では大文字と小文字を区別すべきか?という、ユーザーの体験や価値とは関係のない議論に繋がると思うからです。
単数形? 複数形?
パスを単数形で表現するか、複数形で表現するか、という論点です。
/user/john123
/users/john123
あまり目くじら立てたくないですが、どちらかというと、階層構造に着目すべきと思います。
つまり、階層の考え方からすると、上位が下位を包含しているべきなので、ほとんどの場合で複数形が好ましいです。
以上、Web API のエンドポイントを設計する際の注意点などをまとめました。
これから SPA を開発する方の参考になれば嬉しいです。