1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GraphQL入門-3:REST APIとの比較(2) - 「アンダーフェッチ」問題

Last updated at Posted at 2025-12-02

はじめに

本記事は、自分の備忘録も兼ねて書いてます。

第2回は、REST APIの課題として、不要なデータを取得しすぎてしまう「オーバーフェッチ」について記載しました。

今回は、その正反対とも言える「アンダーフェッチ (Under-fetching)」、すなわち「一度のリクエストではデータが足りない」という問題について見ていきます。


【動作環境】

本記事も、第1回で作成したapi.py(Flaskサーバー)が、以下の環境で起動していることを前提とします。

  • サーバー: python api.py を実行し、http://127.0.0.1:5000/ で起動中
  • クライアント: curl コマンドが実行可能なターミナル

「アンダーフェッチ」について

まず、「アンダーフェッチ」とは何か。
これは、APIにデータを要求した際に、クライアント(アプリケーション)が必要とする情報を一度の通信で揃えられず、データが不足することです。

その結果、クライアントは必要な情報をすべて揃えるために、複数回のリクエスト(API呼び出し)を強制されることになります。

1. デモ

この「複数回のリクエスト」の例を見ていきましょう。

api.py
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)」について記載します。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?