graphQL

はじめてのGraphQL

GraphQLとは、Facebookが開発したAPI向けのクエリ言語です。RESTに変わるサーバーとクライアントの通信手段として注目を集めています。 リクエスト・レスポンスの型の定義ができる、フロント側から取得するデータを選択することができるなどの特徴があります。 GraphQL自体はSQLのようなクエリ言語としての位置づけなので、Java、Node.js、Ruby、JavaScript、Pythonなど様々な言語で利用することができます。

GraphQLとは

GraphQL とは、Facebook が開発した API 向けのクエリ言語です。REST に変わるサーバーとクライアントの通信手段として注目を集めています。 リクエスト・レスポンスの型の定義ができる、フロント側から取得するデータを選択できるなどの特徴があります。

GraphQL 自体は SQL のようなクエリ言語としての位置づけなので、Java、Node.js、Ruby、JavaScript、Python などさまざまな言語で利用できます。

最近では、NetFlix が REST をやめて GraphQL に乗り換えた事例が記憶に新しいです。

なぜRESTではなくGraphQLを使うのか

GraphQL は、当時 RESTful な API サーバーを利用していた Facebook がクライアント/サーバーアプリケーションの性能上の課題とデータ構造の要件を満たす解決策として開発されました。

REST にどのような課題があって、それを GraphQL はどのように解決するのかという視点で見ていきます。

RESTの課題

大きすぎるレスポンス

REST の課題の 1 つは、レスポンスが大きすぎることです。

例えば、PokeAPIからピカチュウのデータを取得する GET リクエストを発行すると次のようなレスポンスが返されます。

https://pokeapi.co/api/v2/pokemon/pikachu

{"abilities":[{"ability":{"name":"static","url":"https://pokeapi.co/api/v2/ability/9/"},"is_hidden":false,"slot":1},{"ability":{"name":"lightning-rod","url":"https://pokeapi.co/api/v2/ability/31/"},"is_hidden":true,"slot":3}],"base_experience":112,"forms":[{"name":"pikachu","url":"https://pokeapi.co/api/v2/pokemon-form/25/"}],"game_indices":[{"game_index":84,"version":{"name":"red","url":"https://pokeapi.co/api/v2/version/1/"}},{"game_index":84,"version":{"name":"blue","url":"https://pokeapi.co/api/v2/version/2/"}},{"game_index":84,"version":{"name":"yellow","url":"https://pokeapi.co/api/v2/version/3/"}},{"game_index":25,"version":{"name":"gold","url":"https://pokeapi.co/api/v2/version/4/"}},{"game_index":25,"version":{"name":"silver","url":"https://pokeapi.co/api/v2/version/5/"}},{"game_index":25,"version":{"name":"crystal","url":"https://pokeapi.co/api/v2/version/6/"}},{"game_index":25,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"game_index":25,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"game_index":25,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"game_index":25,"version":{"name":"firered","url":"https://pokeapi.co/api/v2/version/10/"}},{"game_index":25,"version":{"name":"leafgreen","url":"https://pokeapi.co/api/v2/version/11/"}},{"game_index":25,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"game_index":25,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"game_index":25,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"game_index":25,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"game_index":25,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"game_index":25,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"game_index":25,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}},{"game_index":25,"version":{"name":"black-2","url":"https://pokeapi.co/api/v2/version/21/"}},{"game_index":25,"version":{"name":"white-2","url":"https://pokeapi.co/api/v2/version/22/"}}],"height":4,"held_items":[{"item":{"name":"oran-berry","url":"https://pokeapi.co/api/v2/item/132/"},"version_details":[{"rarity":50,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"rarity":50,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"rarity":50,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"rarity":50,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"rarity":50,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"rarity":50,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"rarity":50,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"rarity":50,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"rarity":50,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"rarity":50,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}}]},{"item":{"name":"light-ball","url":"https://pokeapi.co/api/v2/item/213/"},"version_details":[{"rarity":5,"version":{"name":"ruby","url":"https://pokeapi.co/api/v2/version/7/"}},{"rarity":5,"version":{"name":"sapphire","url":"https://pokeapi.co/api/v2/version/8/"}},{"rarity":5,"version":{"name":"emerald","url":"https://pokeapi.co/api/v2/version/9/"}},{"rarity":5,"version":{"name":"diamond","url":"https://pokeapi.co/api/v2/version/12/"}},{"rarity":5,"version":{"name":"pearl","url":"https://pokeapi.co/api/v2/version/13/"}},{"rarity":5,"version":{"name":"platinum","url":"https://pokeapi.co/api/v2/version/14/"}},{"rarity":5,"version":{"name":"heartgold","url":"https://pokeapi.co/api/v2/version/15/"}},{"rarity":5,"version":{"name":"soulsilver","url":"https://pokeapi.co/api/v2/version/16/"}},{"rarity":1,"version":{"name":"black","url":"https://pokeapi.co/api/v2/version/17/"}},{"rarity":1,"version":{"name":"white","url":"https://pokeapi.co/api/v2/version/18/"}},{"rarity":5,"version":{"name":"black-2","url":"https://pokeapi.co/api/v2/version/21/"}},{"rarity":5,"version":{"name":"white-2","url":"https://pokeapi.co/api/v2/version/22/"}},{"rarity":5,"version":{"name":"x","url":"https://pokeapi.co/api/v2/version/23/"}},{"rarity":5,"version":{"name":"y","url":"https://pokeapi.co/api/v2/version/24/"}},{"rarity":5,"version":{"name":"omega-ruby","url":"https://pokeapi.co/api/v2/version/25/"}},{"rarity":5,"version":{"name":"alpha-sapphire","url":"https://pokeapi.co/api/v2/version/26/"}},{"rarity":5,"version":{"name":"sun","url":"https://pokeapi.co/api/v2/version/27/"}},{"rarity":5,"version":{"name":"moon","url":"https://pokeapi.co/api/v2/version/28/"}},{"rarity":5,"version":{"name":"ultra-sun","url":"https://pokeapi.co/api/v2/version/29/"}},{"rarity":5,"version":{"name":"ultra-moon","url":"https://pokeapi.co/api/v2/version/30/"}}]}],"id":25,"}
# 省略

これは実際のレスポンスの本の一部であり、実際にはもっと巨大です。本体クライアントが必要なデータは名前、タイプだけだったかもしれませんが、大量の必要のないデータを受け取る必要があります。

{
  "name": "pikachu",
  "types": ["electric"]
}

少なすぎるレスポンス

今度は逆にレスポンスが少なすぎるという問題が起こる可能性があります。 例えば、ピカチュウがどのような進化をするのかデータを取得したくなるかもしれません。

PokeAPI で進化に関する情報を取得するにはまずhttps://pokeapi.co/api/v2/pokemon/pikachuのデータを取得したうえで、species に関する情報を取得します。

 "species": {
    "name": "pikachu",
    "url": "https://pokeapi.co/api/v2/pokemon-species/25/"
  },

取得した species の情報からさらに API リクエストを送る必要があります。evolution_chain という情報からピカチュウの進化情報が取得できるので、さらに API リクエストを送ります。

 "evolution_chain": {
    "url": "https://pokeapi.co/api/v2/evolution-chain/10/"
  },

ようやくここでピカチュウはピチューから進化してさらにライチュウに進化するという情報が得られました。ピチューとライチュウの詳細情報を得るには、さらに 2 回 API リクエストを送る必要があるでしょう。

"is_baby": false,
 "species": {
   "name": "raichu",
   "url": "https://pokeapi.co/api/v2/pokemon-species/26/"
},
"is_baby": true,
"species": {
  "name": "pichu",
  "url": "https://pokeapi.co/api/v2/pokemon-species/172/"
}

このように 1 回で十分な情報が取得できないために、いくつもの余分なリクエストを送信しなければなりません。またそれぞれの実際のレスポンスには当然不必要な情報もたくさん含まれています。

エンドポイント、ドキュメントの管理

最後の問題点として、エンドポイント、ドキュメントの管理の煩雑さが挙げられます。REST API はクライアントに変更が加わるたびに新しいエンドポイントを作成する必要があり、エンドポイントの数が膨れ上がってきます。

また適切にドキュメントが更新されていればよいですが、エンドポイントの数が多くなるに釣れスキーマなどを管理・保守することは大変になってきます。

GraphQLはどのように解決するのか

これらの REST API の問題点について、GraphQL がどのように解決するのかを見ていきましょう。 Poke API の代わりにGraphQL Pokemonを使っていきます。

クエリの発行は、プレイグラウンドからブラウザで試すことができます。

スクリーンショット 20210117 22.03.46.png

ピカチュウのタイプを取得

それでは、REST API の例と同様にピカチュウの名前とタイプを取得してみましょう。 GraphQL には、QueryMutationSubscription の 3 つのクエリが存在します。GET リクエストに相当するものが Query です。

Query を使ってデータを取得しましょう。

query {
  pokemon(name: "Pikachu") {
    name
    types
  }
}

query コマンドによって、pokemon というスキーマのデータを取得しようとしています。(name: "Pikachu") によって、特定のデータを取得するように指示します。 さらに、取得するフィールドを宣言しています。ここでは、name(名前)と types(タイプ)フィールドを宣言しています。これは SQL の SELECT 文とよく似ており、宣言したフィールド以外はレスポンスに含まれません。

レスポンスは次のようになります。

{
  "data": {
    "pokemon": {
      "name": "Pikachu",
      "types": [
        "Electric"
      ]
    }
  }
}

ピカチュウの進化情報を取得

取得する情報に evolutions を含めると、進化情報を pokemon 型で取得できます。

{
  pokemon(name: "Pikachu") {
    name
    types
    weight {
      minimum
      maximum
    }
    height {
      minimum
      maximum
    }
    image
    evolutions {
      name
      types
      weight {
        minimum
        maximum
      }
      height {
        minimum
        maximum
      }
      image
    }
  }
}
 

レスポンスは次のとおりです。(Poke API と異なり GraphQL Pokemon は第一世代の情報しか含まれていないのでレスポンスにピチューは含まれていません・・・)

一度のリクエストだけで、必要なすべての情報が取得できていることがわかります。

{
  "data": {
    "pokemon": {
      "name": "Pikachu",
      "types": [
        "Electric"
      ],
      "weight": {
        "minimum": "5.25kg",
        "maximum": "6.75kg"
      },
      "height": {
        "minimum": "0.35m",
        "maximum": "0.45m"
      },
      "image": "https://img.pokemondb.net/artwork/pikachu.jpg",
      "evolutions": [
        {
          "name": "Raichu",
          "types": [
            "Electric"
          ],
          "weight": {
            "minimum": "26.25kg",
            "maximum": "33.75kg"
          },
          "height": {
            "minimum": "0.7m",
            "maximum": "0.9m"
          },
          "image": "https://img.pokemondb.net/artwork/raichu.jpg"
        }
      ]
    }
  }
}

エンドポイントとドキュメントの管理

GraphQL には、常に 1 つのエンドポイントしか存在しません。つまり、エンドポイントの管理の問題は考える必要はありません。

また GraphQL のドキュメントはスキーマに応じて自動で作成されます。プレイグラウンドの右上にある Doc をクリックしてみてください。

スクリーンショット 20210117 22.17.48.png


Contributors

> GitHub で修正を提案する
この記事をシェアする
はてなブックマークに追加

関連記事