Xでこんな投稿を見かけた(https://x.com/jahirsheikh8/status/2070849696730607695)。バックエンドの面接問題という体裁で、二つのAPI設計のうち「一方の方がはるかにスケールする。あなたはどちらを選ぶか」と問うものだ。Aはリソースをパスで分ける設計である。
GET /users/profile
GET /users/settings
GET /users/orders
対するBは、同じエンドポイントにクエリパラメータで種類を指定する設計だ。
GET /users?type=profile
GET /users?type=settings
GET /users?type=orders
僕は秒でA一択だと思った。ところがリプライを覗いてみると、「Bの方が圧倒的にスケーラブル」と書かれている。どうやら同じ「スケーラブル」という言葉で、まるで違うものを指しているらしい。いわば同じ単語の、方言違いである。
A派のスケーラビリティ
A派が言うスケーラビリティは、API設計としてのそれである。リソースが分かりやすく、レスポンスの型が固定される。OpenAPIも書きやすく、SDKも生成しやすい。要するに、保守がしやすいのだ。
たとえばGET /users/profile を見れば、Profileを取得するAPIだと一目で分かる。新しい機能が増えても、次のように素直に足していける。
GET /users/security
GET /users/preferences
GET /users/notifications
B派のスケーラビリティ
一方、投稿者が言っていたのはインフラ構成の話のようだ。API Gatewayを一つ置き、届いたリクエストを種類ごとに振り分ける。/users?type=profile ならProfile Serviceへ、/users?type=settings ならSettings Serviceへ、という具合だ。入口となるInterfaceは一つのまま、裏側のサービスだけを増やしていけるという考え方である。
つまり、エンドポイントを増やさずに済むという意味で、スケーラブルと言っているのだろう。それはそれで一つの理屈ではある。インフラの方言で聞けば、Bの言い分も通じなくはない。
インフラの事情はエンドポイントに持ち込まない
そもそもAPIはApplication Programming Interfaceの略で、アプリケーション同士がやり取りするためのインターフェースだ。Interfaceである以上、API設計はアプリケーション設計の一部である。インフラの設計とは切り離して考えるべきだ、と僕は思っている。
それに、B派の言う構成はAでもそのまま実現できる。
/users/profile
/users/settings
/users/orders
やることは単純だ。API Gatewayで /users/profile をProfile Serviceへ、/users/settings をSettings Serviceへ、/users/orders をOrder Serviceへとルーティングすればよい。サービスの分割やスケールアウトは、URLの設計とは独立した話なのである。Aだからスケールできない、Bだからスケールする、という話ではまったくない。
Bを育てた先にあるGraphQL
もっとも、Bの設計を突き詰めていくと、GET /users?type=profile だけでは済まなくなる。そのうち、こういうものが現れる。
GET /users?type=profile,settings
あるいは、こうだ。
GET /users?type=profile&fields=name,email
これは「なにを返すか」をクエリパラメータで指定する設計である。ここまで来ると、RESTというよりGraphQLの発想に近い。GraphQLでは、取得したいデータをクライアント側が指定する。
query {
me {
profile {
name
}
settings {
theme
}
}
}
となると、返すものをクエリで切り替えるという考え方そのものが、GraphQL寄りなのだ。Bを選んだチームは早晩、自前のGraphQLもどきをいささか不格好に誂えることになる。それなら最初から本物を使った方が話が早い。
以上、AかBかという投稿をきっかけに考えたことを綴った。いちばん面白かったのは、どちらが正しいかではない。API設計としてのスケーラビリティと、インフラ構成としてのスケーラビリティ。別々の意味で、同じ言葉が使われていたことだ。
この二つを混ぜたままでは、議論は永遠に噛み合わない。互いに相手の方言を、ただの訛りだと思い込んでいるようなものである。
個人的な好みも込みで言えば、REST APIとして設計するなら、Aの方が自然だと感じる。リソースごとに責務とレスポンスをはっきり分けられるからだ。同じ言葉で話しているからといって、同じことを話しているとは限らないのだ。