2021/08/20

GraphQLを学ぶ①

@ 酒井悠宇

GraphQL入門① GraphQL Playgroundに触れよう

西川さんの記事でGraphQLを勉強します!

GraphQLとは

GraphQLとは、Facebookが開発しているWeb APIの為の規格で、「クエリ言語」と「スキーマ言語」から構成される。

クエリ言語とは

Wikipediaからの引用

問い合わせ言語(といあわせげんご、英: query language)とは、コンピュータのデータに対して問い合わせをするためのコンピュータ言語である。データの構造(データモデル)によってさまざまである。たとえば、関係データベースに対する問い合わせ言語は、関係代数の集合演算、比較、ソートといった機能を持つものが多い。なお、コンピュータのデータベースを扱うためのコンピュータ言語をデータベース言語という。問い合わせ言語とデータベース言語は、概念的に重なる部分もあるが、同義ではない。


ざっくりと理解すると、SQLのようにデータベースに対して問い合わせの命令を送る言語のことと考えて問題ない。

スキーマ言語とは

コトバンクからの引用

《schema language》マークアップ言語で記述する文書の構造定義に用いられる言語。タグや属性などの要素が、どのような役割で用いられているかを定義する。DTDやXMLスキーマが知られる。


「このファイルはこんな構造になってますよ」と言うことが書いてあるファイルの書き方のルールのこと。

なぜGraphQLが使われるのか

ここからなぜGraphQLが使われるのかを考えてみる。

今までのAPI

今まで最も使われていた(現在進行形で最も使われている)WebAPIはRESTAPI。
RESTAPIに代わるものとしてGraphQLが生まれてきたから、GraphQLはRESTAPIのなんらかの課題を解決するために生まれてきたと考えて問題なさそう。
GraphQLが生まれてきた理由を知るためにRESTAPIとその問題点について考えていく。

RESTAPIとは

RESTAPIとは、設計原則であるRESTに基づいてRESTに設計されたAPIのこと。

RESTは以下の4つの設計原則がある。

  • 全てのリソースは一意なURIで表される(リソースファースト)
  • ステートレスであること
  • 情報の内部に別の情報や別の状態へのリンクを含めることができること
  • 情報の操作(CRUD)にHTTPメソッド(GET、POST、PUT、DELETE)を利用すること


それぞれを説明していく。

全てのリソースが一意なURLで表される。とは、なんらかのリソース、つまりはデータに対してアクセスする際に、そのリソースごとの一意なURLを持つと言うこと。
RESTな設計思想では、リソースというデータの実態を重要視して考える。リソースに対して、そのリソースの場所を一意なURIで表現し、そのURIに対してHTTPSメソッドを送ることでリソースを操作する。

例えば、sakaiと言うデータに対してなんらかの処理を行う際に、どのようにアクセスすればいいのだろうか。
まず考えられるのは、このデータを取得するURL、このデータを更新するURL、このデータを新規作成するURI、このデータを削除するURIを作成すること。
以下のようになる。

users/getSakai

users/updateSakai

users/createSakai

users/deleteSakai


このように、sakaiと言う一つのリソースに対して4つのURIを設定する。そして、この各々のURIにアクセスすることで、このリソースの取得・更新、新規作成・削除を行う、と言うもの。

このように定義してもいいが、RESTなAPIはこのような定義を行わない。

RESTなAPIはリソースに対して一意なURIを定義する。

users/sakai


そして、このURIに対してアクセスする際に、主に4つのHTTPSメソッドを使用する。

例えば、users/sakaiに対してHTTPメソッドのGETリクエストを送りデータの取得をおこなう。と言うことが考えられるし、users/sakaiに対してPOSTリクエストを行いデータの更新を行う。と言うことも考えられる。

このように、リソースと言うデータの実態に対して一意のURIを割り振り、このデータの実態に対して、GET・POST・PUT・DELETなどの統一のインターフェースによりリソースの操作を行うようなAPIの規格のことをRESTAPIと呼ぶ。

次にRESTAPIのかかえる課題について解説する。

RESTAPIが抱える課題


過剰な取得

RESTAPIは、データを取得する際に必要がないデータまで取得してしまう可能性がある。

と言うのも、REST APIで取得するデータは一般的にデータベースの都合や、データの構造が優先され、「フロントエンドの画面を描画ずるのに必要なデータを提供する」と言う設計思想のもと作成されたものではないから。

例えば、スター・ウォーズの登場人物の名前と身長の一覧を表示させる画面を描画したいとする。

その際、スカイウォーズの登場キャラクターであるルーク・スカイウォーズのデータを提供するAPIである「https://swapi.co/api/people/1」に対してGETリクエストを送る。

データの実態は面倒くさいので省略するが、このAPIを叩くと「名前」と「身長」だけではなく、ルーク・スカイウォーカーにまつわる全てのデータが返却される。例えば、出演したスターウォーズのバージョンやスターシップの種類などのデータだ。

今回作成するフロントエンドのページを描画するのに必要なデータは「名前」と「身長」だけだが、RESTAPIからしてみれば、そのようなフロントエンド側の都合は知ったことではない。

このように、ページを描画するためにAPIを叩いてデータを取得するのはいいものの、API側の都合で無駄なデータを返却するというのは往々としてよくある。

過少な取得

また、過剰な取得と同様に、RESTAPIでは取得してきたデータが少なすぎると言うことがよくある。

例えば、スター・ウォーズのルーク・スカイウォーカーが出演する映画のタイトル一覧を描画する画面を作成する場合を考える。

当然、スター・ウォーズのルーク・スカイウォーカーが出演する映画のタイトル一覧を、APIを叩いて取得する必要がある。

「https://swapi.co/api/people/1/」のデータを取得すると、ルーク・スカイウォーカーが出演している映画のURIのリストを取得することができr。

しかし、今回取得したいデータは映画のタイトル一覧だ。そのため、取得したURIのリクエストに再びGETメソッドを送って映画の情報を取得する日強がある。

そして映画の情報を取得するAPIを叩くと、映画のタイトルだけでなく映画の情報全てが送られてくる。ここまでで、ルーク・スカイウォーカーのデータを取得するAPIを一回叩き、そのAPIが送られてきたルーク・スカイウォーカーが出演している映画のURLをn回叩いた。

必要だったのは映画のタイトルだけだったのにもかかわらず、n + 1回のデータの取得が行われた。また、映画のタイトルだけではなく、映画の登場人物を取得する必要がある場合は、さらに映画のデータを取得するAPIから取得された「登場人物のリスト」から、m回のGETリクエストを送る必要がある。

ここまでで、1 + n + (n x m)回のクエリを発行することになった。その結果、レスポンスが遅くなってユーザー体験が低下してしまう。

GraphQLでは、必要なデータを入れ子構造のクエリで取得するため、このようなことは起きづらい。

RESTのエンドポイントの管理が面倒くさい件


先ほどの例で、既存のAPIから、ルーク・スカイウォーカーが出演している映画のタイトルを取得する方法を解説した。

このようにデータを取得してきてもよいが、「映画のタイトル一覧」と言うフロントエンドのページを描画するために、新たに「あるキャラクターに基づく映画のタイトル一覧」を取得できるAPIを発行しても構わない。

例えば、以下のようにURIが考えられる。

/api/character-with-movie-title  // あるキャラクターに紐づく映画のタイトル一覧を取得するAPI


このようなAPIをフロントエンドエンジニアと都度相談して発行するケースも考えられるが、正直なところ面倒くさい。

エンドポイントが増幅していくし、名前の管理もややこしくなる。GraphQLは単一のエンドポイントに対してクエリを送るため、エンドポイントの管理は簡単。

次はGraphQLの利点について見ていく。

GraphQL Playgroundを使ってみよう

次にGraphQL Playgroundを使ってみる。

以下のURLより、GraphQL Playgroundをダウンロードするサイトにアクセスできる。

https://www.electronjs.org/apps/graphql-playground



左のダウンロードボタンでローカルにダウンロードできる。

GraphQL Playgroundを起動した後は、URL ENDPOINTを選択し、「https://graphql-pokemon2.vercel.app」を入力してOPENを行う。これでポケモンのデータを取得するGraphQLのAPIを指定することができた。


以下のような画面になる。



それでは、一つずつ領域を確認してみる。

クエリを記述する場所

左の領域はクエリを記述する場所。

クエリとは、GraphQLがAPIに送る命令のことで、データの取得を行うQueryや、データの新規作成・更新・削除を行うMutation、API側の変更を検知してフロントに通知するSubscriptionなどがある。

それらのクエリを記述する場所は左の領域。


クエリ変数を定義する場所

GraphQLには、クエリ変数というものがある。

クエリ変数は、Query・Mutation・Subscriptionなどのトップレベルのクエリの引数として渡すことができる変数である、オブジェクト形式で定義する。

APIから取得してきたデータが表示される場所

GraphQLのAPIを叩いて取得してきたデータは、右側の領域に表示される。


DOCS、SCHEMA

GraphQLの右側の領域にあるDOCSとSCHEMAを用いることで、APIのスキーマの構造を確認し、送信するべきクエリを判断することができる。

まずはDOCSを確認してみる


queryの中に、pokemonsと言うフィールドとpokemonというフィールドが存在するのが確認できる。
また、pokemonsと言うフィールドはfirstと言う整数型を引数に持ち、戻り値としてPokemo型のリストを返却することが確認できる。
またPokemon型の中身を確認することもできる。

これを元にクエリを定義してみる。

query {
  pokemons(first: 2){
    name
    types
  }
}


まず最初にqueryを指定している。クエリのトップで指定するのは、データの取得を行うqueryか、データの新規作成・更新・削除を行うmutationか、データのサブスクリプションを行うsubscription。
今回はデータの取得を行うqueryを指定している。今回、、queryで指定できるフィールドはpokemonsとpokemonの2つ。今回はpokemonsを指定している。

フィールド値としてpokemonsを指定した場合、返却されるデータの方は、Pokemonと言うデータ型のリストになっている。

今回はPokemon型のデータのうち、nameとtypesと言うフィールド値のみを取得する。そのため、返却されるデータは、Pokemon型のデータの内、nameとtypeと言うフィールド値のみを取得したオブジェクトにリストになる。

また、pokemonsと言うフィールド値に引数として整数型の2を渡してあげることで、取得するデータのフィルタリングを行なっている。

このクエリをエンドポイントに対して送ると、以下のデータが返却される。

{
  "data": {
    "pokemons": [
      {
        "name": "Bulbasaur",
        "types": [
          "Grass",
          "Poison"
        ]
      },
      {
        "name": "Ivysaur",
        "types": [
          "Grass",
          "Poison"
        ]
      }
    ]
  }
}


このように、クエリで指定したデータが取得されていることがわかる。またDOCSを確認することで、どのようにクエリを書けばいいのかもわかりやすい。

ついでにSCHEMAも確認する。

これはGraphQLの作成者が記述したもの。GraphQLは、クエリ言語とスキーマ言語から構成されるWebAPIの規格のこと。先ほど、データを取得するために記述したのがクエリ言語。
以下のSCHEMAは、GraphQLの作成者が記述したスキーマ言語。


このように、SCHEMAにはGraphQLで取り扱う全てのデータが型として記述されている。データは木の構造になっており、トップのデータ型はQuery・Mutation・Subscriptionのいずれかがになっている。

GraphQLのスキーマは、クエリに対応している。ここでは解説しないが、GraphQLのスキーマはリゾルバと呼ばれる関数とも対応しており、リゾルバもスキーマに対応するように記述する。

クエリはスキーマに対応するように記述され、そのクエリが実行されると対応するリゾルバが実行されることにより、データが返却される。リゾルバとスキーマはGraphQLの設計者が記述するもので、フロントはそれに対してクエリを発行するだけ。

クエリをスキーマに対応するように記述し、またクエリの実行により発火するリゾルバもスキーマに対応するように記述する。それにより、スキーマを通じてGraphQL設計者が、GraphQL使用者に対して、APIの使い方を伝えることができる。

GraphQL使用者が記述するクエリと、そのクエリにより発火するリゾルバをスキーマに紐づけることで、スキーマがバックエンドとフロントエンドを繋ぐドキュメントとしての役割を果たす。

次にGraphQLの利点について解説する。

GraphQLの利点

クエリとレスポンスに対応関係がある。

GraphQLの利点の一つに、クエリとレスポンスに対応関係があることが挙げられる。

先ほどGraphQL Playgroundを使用する際に確認したように、クエリとそのレスポンスには明確な対応関係がある。クエリをわざわざオブジェクト形式で書くと言うのは冗長なように見えるが、返却されるデータがオブジェクト形式のデータであることを考えると、妥当なようにも感じる。

クエリを書く際は、スキーマに対応するように記述する。スキーマは、Query・Mutation・Subscriptionをトップに置いた入れ子の構造になっているため、クエリもそのスキーマに対応するように記述する。

クエリとレスポンスに明確な対応関係があることで、後からコードを読む際にAPIのリファレンスに目を通さなくても返却されるデータがなんとなく理解できる。

これは大きなメリット。

スキーマの存在・スキーマの利用をサポートするツールの充実度

GraphQLの最大の特徴は、スキーマが存在すること。また、そのスキーマの中には、各々の型の説明も記述することができる。

GraphQLFoundationが提供するGraphQL(グラフィクル)と言うIDE(総合開発環境)や先ほど使用したGraphQL Playgroundなどのツールを使えば、クエリを発行してその結果を確認するのみならず、スキーマを通じたドキュメントの作成やクエリの補完機能などを使用することができる。

これらの機能も、スキーマを持つ等GraphQLの特性によるもの。

© 2021 powerd by UnReact