GraphQLについて

  • こんにちは、spotチームの名嘉眞です。spotチームはキャンプ場検索サービス(hinata spot)を開発しており、私はバックエンド担当として日々Goを書いてます。今回は業務でGraphQLを使っていることと、以下の書籍を読んだので、改めてGraphQLについて基本的なことをまとめてみました。

  • 初めてのGraphQL ――Webサービスを作って学ぶ新世代API

GraphQLとは

  • APIのための問合せ言語 クエリを実行してデータを呼び出すためのランタイムとも言われます。

  • 一般的にはHTTPプロトコルを使用

  • GraphQLは、クライアント/サーバー通信のための言語仕様。なので実際に実装するときは、GraphQLの言語仕様に則って、クライアント側とサーバー側の実装を行います。

グラフ理論について

GraphQLのメリット

書籍を読んでGraphQLのメリットについてまとめると以下のようになるかなと思いました。

  1. 1回のクエリで必要なフィールドだけ取得できる

  2. 1回のクエリで複数の異なる種類(定義された型)のデータを受け取ることができる

  3. GraphQLスキーマで定義された型でバリデーションされる

  4. GraphQLスキーマがフロントエンドとバックエンドの共通のスキーマ設計書とすることができる

  5. いろいろな言語に対応している。クライアント側とバックエンド側で言語が違っても大丈夫

ひとつずつ詳しく書いていこうと思います。

1. 1回のクエリで必要なフィールドだけ取得できる

 GraphQLのスキーマ定義に方法についてはこの記事で説明しませんが、以下のようなスキーマ定義がされているとします。これはキャンプ場(spot)という型を定義しています。

# キャンプ場を定義した型、Spot型はid, name, catchPhrase, description, addressフィールドをもつ
type Spot {
  id: ID!
  name: String!
  catchPhrase: String!
  description: String!
  address: Address!
}

 このSpot型のオブジェクトを取得しようとした時、REST APIだと、http://example.com/api/spots/:id という感じになり、1つのオブジェクトの全てのフィールドが取得されると想像できます。

 ただ、本来はnameとcatchPhraseだけ取得できれば問題ないなど、全てのフィールドの取得は必要ない場面は多いかもしれません。

 GraphQLでは下記のように、取得したいフィールドを指定してリクエストを送ることができます。今回の例ではほとんど差はないですが、余分なデータを取得しないことでより高速にレスポンスを受け取ることができます。

# Spot型のnameとcatchPhraseフィールドのみを取得するクエリ
query {
  getSpot(spot: { id: "1" }) {
    name
    catchPhrase
  }
}

2. 1回のクエリで複数の異なる種類(定義された型)のデータを受け取ることができる

 こちらも例をあげて説明してみます。先ほどのキャンプ場(spot)のレビューという関連オブジェクトが定義されているとします。

type Review {
  id: ID!
  spotId: ID!
  userName: String!
  email: String!
  title: String!
  point: Int!
}

type Spot {
  id: ID!
  name: String!
  catchPhrase: String!
  description: String!
  address: Address!
  reviews: [Review!]!  # review型のリストをspot型に定義
}

 REST APIだと基本的には、1度対象となるオブジェクトのデータを取得し、そのIDなどをもとに、関連するオブジェクトを取得すると思います。

 GraphQLでは、スキーマ定義に関連するオブジェクトを定義することで1回のクエリで複数の型のデータを取得することができ、効率良くレスポンスを受け取ることができます。

# Spot型のnameとcatchPhraseフィールドと関連するレビューを取得するクエリ
# クエリの中で、spot型とreview型が入れ子になる
query {
  getSpot(spot: { id: "1" }) {
    name
    catchPhrase
    review {
      userName
      title
      point
    }
  }
}

3. GraphQLスキーマで定義された型でバリデーションされる

 GraphQLではスキーマ定義した型でバリデーションがされるため、例えば以下のように存在しないフィールドを指定してリクエストを送ろうとするとエラーとなります。

# 存在しないhogeというフィールドを指定してリクエスト
query {
  getSpot(spot: { id: "1" }) {
    name
    catchPhrase
    hoge
  }
}

# レスポンス
# {
#   "error": {
#     "errors": [
#       {
#         "message": "Cannot query field \"hoge\" on type \"Spot\".",
#         "locations": [
#           {
#             "line": 3,
#             "column": 5
#           }
#         ]
#       }
#     ],
#     "data": null
#   }
# }

スキーマ定義した内容でそのままバリデーションまで構築できるのはとても便利ですね。

4. GraphQLスキーマがフロントエンドとバックエンドの共通のスキーマ設計書とすることができる

 GraphQLを使うと、定義したGraphQLスキーマをもとに共通認識を持って、フロントエンドもバックエンドも開発することができます。(スキーマファースト)

  • スキーマとは... データの型の集合

  • スキーマファーストについて

    • 設計の方法論。スキーマファーストではチーム全員がデータ型について理解していて、バックエンドはデータの永続化とリクエストに応じて返すデータ型が何か、フロントエンドはユーザーインターフェースを組み立てるためのデータ型が何かを理解している必要がある。

 また、GraphQLには イントロスペクション というスキーマの詳細を取得できる機能があります。GraphQL PlayGround などで、Docsとschemaが確認できるのはこの仕組みがあるからです。

 実際に業務でも、GraphQL PlayGroundを使用していまして、この機能のおかげで常に最新のAPIドキュメントを共有して開発ができるのと、APIドキュメントの更新忘れなども発生しないというメリットもあります。(スキーマ定義にコメントも記載でき、コメントも反映されます)

5. いろいろな言語に対応している。クライアント側とバックエンド側で言語が違っても大丈夫

 GraphQLは、クライアント/サーバー通信のための言語仕様と記事の冒頭の方に書きました。あくまで言語仕様です。クライアント側はクライアント側の言語でGraphQLのAPIエンドポイントを叩くように実装する、バックエンドはバックエンド側の言語で、GraphQLのAPIが返すと定義したデータを生成できるように実装するということが可能になります。

 また、GraphQLをさらに便利にするライブラリも充実しています。例えば、GraphQLスキーマからGoのバックエンドのテンプレートを生成してくれるライブラリ、gqlgen などがあります。

まとめ

 GraphQLは、使い方によってはレスポンスをより高速に受け取ることができることや、フロントエンドとバックエンドで共通のスキーマ定義をもとに開発することができるメリットがあります。

 私はvivitに入社してからGraphQLを使い始めましたが、フロントエンドエンジニアとGraphQLスキーマをもとに統一した言葉で会話が出来ることや、入社したばかりでもスキーマ定義を読むことで、なんとなく処理の内容が理解できたりしてGraphQLは素晴らしいと思いました。

 今回の記事では細かいスキーマ定義の方法やクエリの組み立て方は記載しませんでしたが、他にも便利な仕組みやライブラリもあるので、引き続き学んでいこうと思います。