フロントエンド向けに「汎用API」を構築すべきではない理由と実践的アプローチ
1. はじめに:本記事の目的
本記事は、Don’t Build a General Purpose API (4 Years Later) という記事を元に、自社サービスなどのフロントエンド向けに 「汎用目的のAPI(General Purpose API)」 を構築せず、画面(ページ)に特化したAPIを構築するアプローチについての理解を深めるためのものです。
元の記事
このアプローチは、メンテナンスの簡素化、バグの削減、そしてパフォーマンスの向上に大きく寄与することが実証されています。ただ、この概念はしばしば誤解を招くため、よくある懸念や疑問を通じて、正しいアーキテクチャの考え方を学んでいこうという内容です。
2. コアにあるコンセプト:汎用APIとページ専用APIの違い
まず、「私たちが開発すべきなのは、外部の顧客が様々な用途で利用するための汎用APIではなく、自社のフロントエンドチームの要件のみを満たすBFF(Backends For Frontends)APIである。という定義で話を進めていきます。もし公開用の汎用APIが必要な場合は、要件の衝突やリリース管理の負担を避けるために、BFFとは完全に分離して構築することを考えましょう。
以下の図は、従来の汎用APIを用いたアプローチと、推奨されるページ専用APIアプローチの違いを示しています。
❌アクション/リソース単位API中心 → ✅ 画面(UIユースケース)単位API中心
❌レスポンスは「必要なデータを返す」→ ✅「最初からUI専用の形で組み立てて返す」
3. よくある誤解と設計時の重要ポイント
① 「非同期で読み込まないとパフォーマンスが落ちる」という誤解
フロントエンドで複数の汎用APIエンドポイントを叩いて非同期にデータを集める手法は、理論上は良く見えますが、実際には課題があるということ。
- 課題: 1つのページを描画するために10個のAPIリクエストを並列に送ることは、ネットワークの競合や不安定な遅延を引き起こします。
- 解決策: バックエンドで、必要なデータを1つのバンドルとしてまとめましょう。バックエンドとDB間の通信でデータをまとめる方が、フロントエンドから何度もリクエストを送るより数百倍から数千倍高速(例:1回の200msの通信で完結)になります。また、バックエンド側での最適化やキャッシュも簡単になります。
② 「JSONペイロードにHTMLを含めるべきだ」という誤解
このアプローチは、サーバーからHTMLそのものや、スタイル情報をJSONに変換して送ることを意味しているわけではありません。
- 正しいJSONの設計: フロントエンド側で静的なコンテンツや状態管理(HTMLの構造やTailwindなどのスタイル)はハードコードし、バックエンドからは「画面の穴埋め」に必要な動的データ(コンテンツ)のみをJSONで提供するということです。
- 過剰な正規化は避ける: ページ間でJSONの構造を無理に統一しようとしてはいけません。それぞれのページが要求する形式に合わせて、カスタムコードでJSONを返すようにすることをおすすめします。(JSONはページを構築するための引数にすぎない)。
汎用API(ドメイン中心)
{
"users": [],
"posts": [],
"comments": []
}
✅ 専用API(UI中心)
{
"heroSection": {...},
"recommendedPosts": [...],
"sidebar": {...}
}
③ 「フロントエンドの柔軟性が失われる」という誤解
「汎用APIがあれば、フロントエンドチームがバックエンドに頼らずに自由に新機能やデザイン変更を行える」というのは誤解を招く表現です。
- バックエンドの負債化: フロントエンドが古いエンドポイントを予期せぬ方法で組み合わせて使い始めると、バックエンドはどのAPIがどう使われているか追跡できなくなります。その結果、古いAPIを削除できなくなり、保守にかけるコストが膨大になります。
- ページ専用APIの利点: 画面ごとに必要なデータを返す設計であれば、ページ単位での再設計が極めてシンプルになり、他の画面への意図しない影響(副作用)を気にする必要がなくなります。
④ 「SPAには不向き」という誤解
SPAにおいても本質は変わりません。「ページ」という言葉を 「スクリーン(画面)」 に置き換えて考えてみましょう。
- 画面遷移時には、次の画面に必要なデータを1つのバンドルとしてフロントエンドに渡します。
- 画面内の特定のセクションだけを更新したい場合は、そのセクション専用のエンドポイントを用意しても構いませんが、多くの場合、画面全体のデータを再取得して必要な部分だけを差し替える方が高速でシンプルです。
⑤ 「GraphQLやBFF用の集約層(Aggregation Layer)を導入すればよい」という誤解
- 集約層の無駄: 汎用APIを作った上で、それをまとめるための層(Aggregation Layer)を別途作るのは、バックエンドのコード量と複雑さを2倍、3倍にするだけであり推奨されません。
- GraphQLのオーバースペック: GraphQLは、FacebookのようにPC、スマホ、テレビ、家電など「無数の異なるクライアント」に対して無限の柔軟性を提供する必要がある企業には適しています。しかし、一般的なWebサイトとモバイルアプリを1つずつ運用しているようなケースでは、無限の柔軟性を持つクエリ言語の保守・セキュリティ管理はオーバースペックであり、単に「フロントエンドが必要とするデータを提供する」というシンプルな解決策をとるべきです。
⑥ 「DBレコードをそのままCRUDとして公開する方が保守しやすい」という誤解
データベースのレコードを直接フロントエンドに公開し、CRUD操作を委ねることは、開発が「楽」になったような感じになりますが、実際にはアプリケーションのロジックやデータ結合の負担、ネットワークエラーのハンドリングをすべてフロントエンドに押し付けている場合が多いです。
- 正しいリソースの捉え方: CRUDはデータベースのレコードに対してではなく、意味のある「リソース」 に対して行うべきです。1つの「ページ」を読み取り専用(READ)のリソースとして扱ったり、複数のDBレコードをまとめた「フォームオブジェクト」を更新用のリソースとして扱うなど、より抽象度の高いレベルでAPIを設計することをおすすめします。
4. まとめ
フロントエンドの開発においては以下の点を考慮することで、堅牢でパフォーマンスが高く、保守しやすいシステムを構築を再現できます。
- 画面(ページ/スクリーン)が主役: 各画面が必要とする専用のデータ構造(JSON)を、バックエンドから1回の通信で提供する。
- 役割分担の明確化: 表示構造やUIの状態はフロントエンド(React等)で管理し、動的データのみをバックエンドから渡す。
- 過剰な汎用化の排除: 汎用APIやGraphQL、DB直結のCRUDは、自社フロントエンド向けとしては複雑すぎるか、技術的負債の原因になるため避ける。
Discussion
記事ありがとうございます。これってGraphQLの思想(クライアントが欲しいデータを返せるように柔軟なAPIを構築する)に一部反している気がしますが、GraphQLの思想についてどう思いますか?
思想は反していないと思いますよ。GraphQLを使用する規模やタイミングの話かと。
「オーバースペック」になってしまうと表現されてる部分です。
「FacebookのようにPC、スマホ、テレビ、家電など「無数の異なるクライアント」に対して無限の柔軟性を提供する必要がある企業には適しています。しかし、一般的なWebサイトとモバイルアプリを1つずつ運用しているようなケースでは、無限の柔軟性を持つクエリ言語の保守・セキュリティ管理はオーバースペックであり.....」なので、別にGraphQLの思想に反しているわけではないかと。