今更ながらGraphQLについてまとめてみた


はじめに

GraphQLについてのオライリー本を読んだり、AppSyncを趣味で触ったりしたので今更ながらGraphQLについて社内勉強会用にまとめてみました。


GraphQLとは

GraphQLを一言で説明するとWEB APIのためのクエリ・スキーマ言語である。
クライアントアプリケーションのデータモデルの機能と要件を記述することを目的にFacebookの開発チームによって作成された。1


クエリ言語とスキーマ言語

ここで扱うクエリ言語スキーマ言語については以下のようなイメージとなっている。

  • クエリ言語
    • クライアントアプリがGraphQLサーバーにリクエストを送信するための構文
  • スキーマ言語
    • GraphQLサーバーのデータ型の集合を定義するための構文

GraphQLの立ち位置

アプリケーション開発に用いられるGraphQLのイメージ。


GraphQLサーバーが実際に行うこと

  • APIサーバーとの通信を行う
    • もしくは直接データベースの操作を行う
  • クエリ構文を解析する
    • 解釈した構文に基づいて処理(リゾルバ)が実行される


WEB APIとしてのクエリ

GraphQLサーバーへ送るクエリはHTTPリクエストのPOSTメソッドを用いて送信される。以下はcURLでの例。

curl --location --request POST 'http://snowtooth.moonhighway.com/' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query {\r\n  allLifts {\r\n    name\r\n  }\r\n}","variables":{}}

クエリのリクエストを試す場合は、GraphiQLGraphQL PlaygroundなどのツールもしくはPostmanのGraphQL機能を使用することができる。


クエリの種類

クライアントアプリケーションが用いるクエリの構文では、3つのオペレーションを用いることができる。

  1. query
  2. mutation
  3. subscription

query

データを取得するためのオペレーション。実際に取得したいデータをfieldに指定する。以下はスノートゥースで、すべてのLiftnamestatusの情報をとってくる例。

query getAllLifts {
  allLifts {
    name
    status
  }
}

mutation

データの書き込み操作を担当する。基本的に構文はqueryと変わらない。以下は前ページで用いたスノートゥースでとあるLiftstatusを更新する例。

mutation openLift {
  setLiftStatus(id:"astra-express" status: OPEN) {
    name
    status
  }
}

subscription

サーバーからリアルタイムでデータを受信することができる。例えばmutationで更新したstatusを毎回受け取りに行くために、一々queryを叩かずともsubscriptionを用いればWebSocketを通じてリアルタイムにデータを受け取ることができる。以下はその構文の例。

subscription listenStatusChange {
  liftStatusChange {
    name
    status
  }
}

ここで省略した話

  • フラグメント
    • 冗長な構文を共通化させる仕組み
  • ユニオン型
    • 2つの異なるオブジェクト型をまとめる仕組み
  • インタフェース
    • 実装すべきフィールドのリストを指定する抽象型

スキーマの設計

実際にGraphQLサーバーを実装するために、まずはデータ型の集合であるスキーマを設計する。実装例については過去の記事の内容を使いまわしてみる。

enum Category {
    HOGEHOGE
    HUGAHUGA
}

type Item {
    id: String!
    data: String!
    category: Category!
}

type Query {
    allItem: [Item]
    allItemOnCategory(category: Category): [Item]
}

input ItemInput {
    data: String!
    category: Category!
}

type Mutation {
    addItem(item: ItemInput!): Item
}

はスキーマの核になるもの。それぞれの型には対応するフィールドが存在する。QueryMutationもそれぞれ使用可能なクエリがフィールドとして定義されている型となる。

type Item {
    id: String!
    data: String!
    category: Category!
}

type Query {
    allItem: [Item]
    allItemOnCategory(category: Category): [Item]
}

type Mutation {
    addItem(item: ItemInput!): Item
}

スカラー型とリスト

それぞれのフィールドが固有の型のデータを持っていて、その組み込みの型をスカラー型という。例えばidにはStringというスカラー型で定義されている。ちなみにString!!を付与することでそのフィールドがnullにならないことを示すことができる。
また、型を[]で囲むことでフィールドに型のリストを指定することができる。例えば[Item]Itemという型のリストであることを示している。


Enum

Enumは限られた選択肢の文字列が入力・出力されることをフィールドに定義したいときに用いる

enum Category {
    HOGEHOGE
    HUGAHUGA
}

ここで作成したCategoryはフィールドの型として用いることができる。


入力型

引数に対して使用する型。多数の引数をまとめるために用いる。

input ItemInput {
    data: String!
    category: Category!
}

ここで作成されたItemInputは引数にのみ用いることができる。


ここで省略した話

  • カスタムスカラー型
    • 独自で定義されたスカラー型。
  • ユニオン型
    • 複数の型のうち一つを返す型。
  • インタフェース
    • 型の中で必ず存在するフィールドを定義する抽象型。

リゾルバ

リゾルバは特定のフィールドを返す関数。クエリで指定されたフィールドに対して発行される。以下は過去の記事で書いたAppSyncとCDKの実装例。

...
    itemDS.createResolver({
      typeName: 'Query',
      fieldName: 'allItem',
      requestMappingTemplate: MappingTemplate.dynamoDbScanTable(),
      responseMappingTemplate: MappingTemplate.dynamoDbResultList(),
    })
    itemDS.createResolver({
      typeName: 'Mutation',
      fieldName: 'addItem',
      requestMappingTemplate: MappingTemplate.dynamoDbPutItem(
          PrimaryKey.partition('id').auto(),
          Values.projecting('item')),
      responseMappingTemplate: MappingTemplate.dynamoDbResultItem(),
    })
...

直感的な理解としては、リゾルバはリクエストとレスポンスのボディに対するマッピング・そのための内部的な処理を実装するものである。


さいごに

  • GraphQLを用いた開発では、スキーマとクエリの2つの言語を書くことになる
    • スキーマはAPIの仕様となるもの
    • クエリはクライアントアプリからのリクエスト
  • 基本的にライブラリを用いた開発となる
    • 手で実装するのはおそらく難解
    • GraphQL用のライブラリについては公式ページで紹介されている。

GraphQLについて気になった問題などを取り上げた記事などの紹介


N+1問題

N+1問題とは「1つのSQLでN件のレコードをフェッチしたあと、それぞれ対して関連するレコードを個別にフェッチするのにNつのSQLを発行している」状態を指す問題です。GraphQLのリゾルバでSQLを用いる場合このが引き起こされやすくなります。
この問題についてはこの記事が参考になりました。


REST APIの代替技術となるのか

こちらの記事にある通り、GraphQLにも欠点があります。

  • フルスクラッチでの実装が難しい(基本はライブラリ依存)
  • 画像や動画などの大容量バイナリの扱いが難しい(JSONなので)

Redux vs GraphQL

この話題についてとあるスライドの28ページ目の表がわかりやすくまとまっていました。
こういった記事にもあるように基本的にはapollo-clientReduxとの対比でよく議論されているようです。