タイトルまま。APIを作り始めてまだ1年にもたたない若輩者だけれど、自分なりに勉強して考えてきたことをまとめる。まぁそのレベルなので初心者向け。Web APIの設計でエンドポイントをどうするか、自分がどういう理屈で考えているのか、一緒にやっている人たちに伝えるための記事であり、備忘録でもある。
極論、APIは一つでなんでもできる
APIのエンドポイントをどうするかって、けっこう考えどころです。APIを作り初めて1年にもたちませんが、もう何度もひっくり返しています。考えれば考えるほど正解が見えてこないというか。まぁ、そもそも正解なんてないのでしょう。なにしろ、極端な話、APIは一つでも良い。
POST /api
これで全部できる。bodyに処理内容を記述すればよい。やったー。が、これが良いと思う人は誰もいない。だって何がしたいのか全然わからない。すべての処理を一つのオブジェクトでやろうとするようなもの。
ということで、わかりやすい名前をつけよう、となります。
たとえばユーザーを追加するAPIを考えた時
こういうのは具体的な例を交えて考えたほうがわかりよいので、「ユーザーを追加する時に叩くAPI」を想定して考えていきます。より具体的には、idがhogeのユーザーを追加するとします。この場合、考えるのはid=hogeをどうやって伝えるかですが、だいたい3とおり考えられるかと思います。
- POST /users
- POST /users/hoge
- POST /users?id=hoge
1のURIからはhogeがわかりません。つまり、bodyでもつことになります。2はパスパラメータにもっています。3はクエリパラメータです。どれも同じことができる。じゃ、どれがいいんだろう。
リソースは具体的な集合という捉え方
まず、クエリパラメータを使ったやり方はあまりよろしくないように見えます。クエリパラメータが省略された場合、デフォルト値があるわけだけれど、追加されるユーザーのデフォルトってなんやねん、という話。といってクエリパラメータを必須にするのもなにか妙。っていうか、必須ならパスパラメータが自然では?と思う。
ということで2のパスパラメータについて考えます。これはこれで悪くないような気がするけれど、自分は使いません。だって、POSTしようとした時、hogeさんはusersの中にいない。
"users"であって"user"ではないことに注意。なぜ複数形かと言えば、リソースは抽象的なモデルではなくて、具体的な集合と考えるから。このあたり、DBのテーブル名は単数形か複数形か議論と同じだと思います。ちなみに、このように考えると集合なのだから動詞ではなく名詞であるべき、とも言えます。
で、具体的な集合であれば、集合の中にいないhogeさんを指定して追加するのは、なんとも妙な話。ということで、ユーザーを追加する時は1の形式を自分はとります。だいたい皆さんそうされているんじゃないかしら。性能的にも、これなら複数人追加できる。
更新の時は?
考え方が分かれそうなのは、更新や削除の時でしょうか。たとえば、hogeさんの情報を変更しようとしたら、どうすればよいだろう。更新なので、HTTPメソッドはPUTを使うとして、リソース名は?先の例にならえば、考えられるのはざっくり3とおり。
- PUT /users
- PUT /users/hoge
- PUT /users?id=hoge
とりあえず、3のクエリパラメータはPOSTの時と同じ理由で却下。更新するユーザーのデフォルトってなによ。
では2のパスパラメータにhogeをもたせるのは。これは良い気がする。今回は、usersの中にhogeさんがいるわけなので、hogeさんをパスパラメータで指定することに何の不自然さもない。
では1は。別にこれでも構わない気がする。むしろ、こちらならばhogeさんだけではなくて、同時に他のユーザーの情報も更新できる。性能的にはむしろ上。
なのだけれど、この場合だと自分は2のパスパラメータにします。というのも、/usersだと集合そのものにはたらきかけているようなイメージになってしまうのですが、/users/hogeならば集合usersの中にいるhogeさんに対してはたらきかけているイメージになって、こちらのほうがしっくりくる。
とはいえ実際問題、100ユーザーについて同時に更新するようなケースが頻発するのであれば、APIを叩くところはボトルネックになりやすいので、その場合は1の形式を採用するかもしれない。なのでまぁ、最終的にはユースケースに応じて、ということになるかと思います。
階層は浅めに
次に、ユーザーのコメントができるAPIを考えてみる。具体的に、id=hogeさんがコメントした時のことを考えてみると…
POST /users/hoge/comments
というのが考えられるかもしれない。しれないが、果たしてURIにhogeを入れる必要はあるだろうか。
POST /comments
で十分ではなかろうか。
特にGETのことを考えると、そのように思える。というのも、hogeさんのコメントを取得したいのであれば、GET /users/hoge/commentsでよいかもしれないが、hogeさんに限らず、コメント一覧を取得したい、と考えると、GET /commentsとしたくなる。hogeさんのコメントだけほしいのであれば、GET /comments?id=hoge とクエリパラメータを使ってやればよい。集合commentsは、集合usersとは切り離して考えたほうが、なにかとやりやすそうです。
階層構造を深くする前に、本当に紐付けなければいけないのか、ユースケースから考えてみるわけです。階層が深いということは、それだけ依存関係があるということ。小回りがききづらくなるし、名前が長いと単純にわかりづらいし、できれば階層は浅いほうがよい。
パスパラメータとクエリパラメータの使い分け
さて、先の例でクエリパラメータを使いました。しれっとGET /commens?id=hogeとしましたが、これはパスパラメータを使っても書けそうです。
- GET /comments?id=hoge
- GET /comments/hoge
どちらもいけそうですが、この場合はクエリパラメータのほうがよいかと思います。というのも、GET /comments/hoge だと、hogeが集合commentsに含まれるcomment_id的なものに見えます。この書き方なら、一つのコメントの詳細が取得できてほしい。パスパラメータは集合の中の一部を取り出すようなイメージで、クエリパラメータは集合全体の中から絞り込むようなイメージでしょうか。
機械的に判断するなら、必須なのか必須じゃないのか、を考えると、自ずと整理されそうです。
パスパラメータはその特性上必ず必要となります。任意の何かを指定するときには、基本的にはパスパラメータが良い。一方、クエリパラメータは省略できます。省略しても良い時、たとえばこの例のように検索条件でフィルタリングしたい時などは、クエリパラメータ、ということになります。
内部の開発者用なら1画面1API、1アクション1API
あともう一つ、APIに大きくわけてLSUDs(Large Set of Unknown Developers)向きとSSKDs(Small Set of Known Developers)向きのものがある、という考え方があります。要は不特定多数の開発者向けと内部の限られた開発者向けのAPI、というです。両者ではAPI設計の基本となる考え方が異なります。前者は汎用性を求めますが、後者はゆーケースがわかっている以上、最小限のAPIで必要十分な情報を取得・送信できることが求められます。
で、検索しているとよく出てくるAPI設計の例は、割とLSUDs的な考え方のことが多いので、注意。SSKDs的には、1画面1APIコール、1アクション1APIコールにできると美しい。
参考
Web API: The Good Partsとても良い本です。急遽APIを作らないといけなくなった人は、とりあえずこの本を読めばだいぶ不安が和らぐのではないかと思います。時間かけて色々ググるより、まずこの本を読んだほうが早いかと。
API設計について調べるとRESTfulとはなんぞやとか使ったことのないHTTPメソッドとかHTTPの細かな仕様とかエラーコードはどうするとか色々出てきて頭パンクしますが、まぁ何事も千里の道もなんとやらで、一つ一つ抑えていかないとですね。
コメント
コメント一覧 (2件)
第7章の、”LSKDs(Large Set of Known Developers)向きとSSKDs(Small Set of Unknown Developers)”の部分にて、Known・Unknownが逆になっています。
誤
・LSKDs(Large Set of Known Developers)
・SSKDs(Small Set of Unknown Developers)
正
・LSKDs(Large Set of “Unknown” Developers)
・SSKDs(Small Set of “Known” Developers)
あれ、ほんとですね。ご指摘ありがとうございます。修正しました。