はじめに
本記事は、自分の備忘録も兼ねて書いてます。
第2回は、REST APIの課題として、不要なデータを取得しすぎてしまう「オーバーフェッチ」について記載しました。
今回は、その正反対とも言える「アンダーフェッチ (Under-fetching)」、すなわち「一度のリクエストではデータが足りない」という問題について見ていきます。
【動作環境】
本記事も、第1回で作成したapi.py(Flaskサーバー)が、以下の環境で起動していることを前提とします。
-
サーバー:
python api.pyを実行し、http://127.0.0.1:5000/で起動中 -
クライアント:
curlコマンドが実行可能なターミナル
「アンダーフェッチ」について
まず、「アンダーフェッチ」とは何か。
これは、APIにデータを要求した際に、クライアント(アプリケーション)が必要とする情報を一度の通信で揃えられず、データが不足することです。
その結果、クライアントは必要な情報をすべて揃えるために、複数回のリクエスト(API呼び出し)を強制されることになります。
1. デモ
この「複数回のリクエスト」の例を見ていきましょう。
from flask import Flask, jsonify
app = Flask(__name__)
# --- データベースの代わりとなるサンプルデータ ---
# ユーザー情報(オーバーフェッチの原因になりやすい)
users_data = {
"1": {
"id": "1",
"name": "佐藤 太郎",
"email": "taro@example.com",
"address": "東京都...",
"joined_date": "2023-01-01"
},
"2": {
"id": "2",
"name": "鈴木 花子",
"email": "hanako@example.com",
"address": "大阪府...",
"joined_date": "2023-03-15"
},
}
# 記事情報
posts_data = {
"p1": {"id": "p1", "author_id": "1", "title": "GraphQL入門", "body": "GraphQLについて解説します。"},
"p2": {"id": "p2", "author_id": "2", "title": "Flaskの使い方", "body": "Flaskは軽量なフレームワークです。"},
}
# コメント情報(アンダーフェッチの原因になりやすい)
comments_data = {
"p1": [ # 記事p1へのコメント
{"id": "c1", "text": "分かりやすいです。"},
{"id": "c2", "text": "勉強になります。"}
],
"p2": [ # 記事p2へのコメント
{"id": "c3", "text": "試してみます。"}
]
}
# -------------------------------------------------
# (1) 特定のユーザー情報を返すエンドポイント
@app.route('/users/<user_id>', methods=['GET'])
def get_user(user_id):
user = users_data.get(user_id)
if user:
# 常にユーザーの「全情報」を返す
return jsonify(user)
else:
return jsonify({"error": "User not found"}), 404
# (2) 特定の記事情報を返すエンドポイント
@app.route('/posts/<post_id>', methods=['GET'])
def get_post(post_id):
post = posts_data.get(post_id)
if post:
return jsonify(post)
else:
return jsonify({"error": "Post not found"}), 404
# (3) 特定の記事のコメント一覧を返すエンドポイント
@app.route('/posts/<post_id>/comments', methods=['GET'])
def get_comments(post_id):
comments = comments_data.get(post_id)
if comments is not None:
return jsonify(comments)
else:
# 記事が存在しない、またはコメントが0件の場合
return jsonify([])
if __name__ == '__main__':
app.run(debug=True, port=5000)
- クライアントの要求:
「記事IDがp1の記事タイトルと、その記事を書いた著者名、コメント一覧が欲しい」
この要求を満たすため、クライアントは以下の手順で処理をします。
手順1:記事情報の取得
記事情報を取得するために、/posts/p1 にアクセス
- 実行コマンド:
$ curl http://127.0.0.1:5000/posts/p1 - APIからのレスポンス:
{ "author_id": "1", "body": "GraphQLについて解説します。", "id": "p1", "title": "GraphQL入門" }
記事のtitleは取得できましたが、「著者名」と「コメント一覧」が足りません。これが「アンダーフェッチ」です。
ただし、著者情報を取得するための手がかりとなる author_id: "1" は得られました。
手順2:著者名の取得
手順1で得た author_id: "1" を使い、著者名を取得するために、/users/1 にアクセス
- 実行コマンド:
$ curl http://127.0.0.1:5000/users/1 - APIからのレスポンス:
{ "address": "東京都...", "email": "taro@example.com", "id": "1", "joined_date": "2023-01-01", "name": "佐藤 太郎" }
著者のnameが取得できました。
(同時に、不要なaddressなども取得しており、これは「オーバーフェッチ」にあたります)
手順3:コメント一覧の取得
最後に、コメント一覧を取得するために /posts/p1/comments にアクセスします。
- 実行コマンド:
$ curl http://127.0.0.1:5000/posts/p1/comments - APIからのレスポンス:
[ { "id": "c1", "text": "分かりやすいです。" }, { "id": "c2", "text": "勉強になります。" } ]
これで、クライアントは3回のリクエストを経て、ようやく「記事タイトル」「著者の名前」「コメント一覧」という3つの情報を揃えることができました。
2. なぜアンダーフェッチが問題なのか
「データが足りない」ことによる最大の問題は、クライアントが欲しい情報を揃えるために、複数回のリクエストが発生した点です。
クライアントとサーバー間の通信は、アプリケーションの応答速度に影響します。
リクエスト回数が増えるほど、ユーザーの待ち時間は長くなってしまいます。
原因分析
この問題は、REST APIの「リソース(情報資源)ごとにエンドポイント(URL)を分離する」という設計思想に起因しています。
api.pyのコードを見ると、「ユーザー」「記事」「コメント」という各リソースが、それぞれ独立したエンドポイントとして定義されています。
api.py のエンドポイント(抜粋):
# (1) ユーザー情報のエンドポイント
@app.route('/users/<user_id>', methods=['GET'])
def get_user(user_id):
# ...
# (2) 記事情報のエンドポイント
@app.route('/posts/<post_id>', methods=['GET'])
def get_post(post_id):
# ...
# (3) コメント情報のエンドポイント
@app.route('/posts/<post_id>/comments', methods=['GET'])
def get_comments(post_id):
# ...
この設計は、機能をシンプルにする、という点では優れています。
しかし、クライアント側が「複数のリソースを横断して」情報を欲しがるようになると、リソースごとに分離されたエンドポイントが、そのままアンダーフェッチ(=リクエスト回数の増加)の原因となってしまいます。
GraphQLによる解決策
GraphQLは、この問題を、「一度のクエリでまとめて要求できる」仕組みで解決します。
それでは、見ていきましょう。
クライアントは、欲しいデータの構造そのものを「クエリ」としてサーバーに送信します。
# クライアントが欲しいデータの構造を「宣言」するクエリ
query {
post(id: "p1") {
title
# 著者(author)情報をネストして要求
author {
name
}
# コメント(comments)情報も要求
comments {
text
}
}
}
このクエリは、「記事(post)」を起点に、それに関連する「著者(author)」の「名前(name)」、さらに「コメント(comments)」の「テキスト(text)」を、1回のリクエストでまとめて取得しようとしています。
GraphQLサーバーは、このクエリを解釈し、必要なデータをすべて組み立てて、一度のレスポンスで返します。
# サーバーからの応答イメージ
{
"data": {
"post": {
"title": "GraphQL入門",
"author": {
"name": "佐藤 太郎"
},
"comments": [
{ "text": "分かりやすいです。" },
{ "text": "勉強になります。" }
]
}
}
}
このように、クライアントは複数回のリクエストの代わりに、たった1回のリクエストで必要な情報をすべて過不足なく取得できます。
まとめ
- アンダーフェッチは、一度のリクエストで必要なデータが揃わないことで、複数回のリクエストを発生させ、パフォーマンス低下につながる
- REST APIでは、「リソースごとにエンドポイントを分離する」設計思想が、複数のリソースをまたぐデータを要求する際に、構造的にアンダーフェッチを発生させやすい
- GraphQLは、クライアントが関連するデータをネスト構造の「クエリ」として一度に要求できる仕組みによって、この問題を解決する
GraphQLは、クライアント側が「欲しいデータの構造」を主体的に定義できるようにすることで、これらの課題を解決し、API通信を効率化する技術です。
次回は、「GraphQLはどのように動くのか(What/How)」について記載します。