GraphQLについて調べてみた | サイバーエージェント 公式エンジニアブログ
こんにちは、秋葉原ラボの鈴木(@brfrn169)です。
普段は、ログ解析基盤のインフラ部分や分散DB(主にHBase)周りをやっています。

今回は、先月の2015年7月にFacebookがRFCドラフト案を公開した「GraphQL」について紹介します。
執筆時点の情報をもとに書いておりますが、GraphQLは現在ドラフト段階なので、今後、変更があるかもしれませんのでご注意ください。

概要

GraphQLは、クライアント・サーバ間でのデータのやりとりを記述するためのクエリ言語です。
GraphQL自体は2012年からFacebookで開発されており、先月の2015年7月にRFCドラフト案を公開しました。

GraphQLの文法は直感的で柔軟であり、学習コストが小さいことが特徴となっています。

百聞は一見にしかずということで、まず以下のクエリをご覧ださい。
user(id: "1") {
  name
  friends {
    name
  }
}
このクエリの意味は、idが"1"のユーザの名前とその友達の名前を取得するというものです。
このクエリのレスポンスは以下になります(Jsonフォーマット)。
{
  "user": {
    "name": "User One",
    "friends": [
      {
        "name": "Friend One"
      },
      {
        "name": "Friend Two"
      }]
  }
}
なるほど、直感的な気はします。
クエリとJsonで返ってきたデータが同じ構造となっていますね。

GraphQLはあくまでもクエリ言語であり、特定のストレージやDBの実装ではありません。
これはSQLと同様だと思いますが、GraphQLのような共通のクエリ言語があることで、アプリケーション開発やツール作成などが容易になるというメリットがあります。

詳細に入る前に、なぜFacebookでGraphQLが開発されたのかについて簡単に説明します。

Facebookでは、ソーシャルグラフにアクセスするためにGraph APIというRESTのAPIが用意されています。これはシンプルで良いのですが、複雑なクエリを書くことが難しいという欠点があります。
また、FacebookではFQLと呼ばれるSQL似た文法でソーシャルグラフにアクセスするインターフェースもあります。ただし、これもJoinのようなことをすると複雑で理解しづらいものになってしまいます。
そこで、より直感的で柔軟なクエリを記述することのできるGraphQLが開発されました。

それでは、GraphQLについて、更に詳細に見て行きましょう。

クエリ

まずは、GraphQLのクエリについて説明します。

以下のクエリをご覧ください。
{
  me {
    name
    age
  }
}
これのレスポンスは以下になります。
{
  "me": {
    "name": "Toshihiro Suzuki",
    "age": 31
  }
}
meは例えば現在ログインしているユーザになります。
上記クエリは、ログインユーザの名前と年齢を取得するものになります。

以下のようにログインユーザではなく、idを指定して取得するようにすることもできます。
{
  user(id: 3) {
    name
    age
  }
}
次は少し複雑な例になります。
{
  me {
    name
    age
    profilePicture {
      width
      height
      url
    }
  }
}
このクエリは、ログインユーザの名前と年齢と、プロフィール画像の情報を取得するものになります。
結果は以下のようになります。
{
  "me": {
    "name": "Toshihiro Suzuki",
    "age": 31,
    "profilePicture": {
      "width": 50,
      "height": 50,
      "url": "https://abc.com/abc.jpg"
    }
  }
}
クエリとレスポンスの構造が一致してるので分かりやすいですね。

次は変数を使ったクエリになります。
{
  me {
    name
    age
    profilePicture(size: 300) {
      width
      height
      url
    }
  }
}
profilePictureのsizeに300を指定して取得しています。
レスポンスは以下になります。
{
  "me": {
    "name": "Toshihiro Suzuki",
    "age": 31,
    "profilePicture": {
      "width": 300,
      "height": 300,
      "url": "https://abc.com/300.jpg"
    }
  }
}
また、以下のようにprofilePictureを複数取得したい場合も、エイリアスを付けることで可能となっています。
{
  me {
    name
    age
    lilPic: profilePicture(size: 50) {
      width
      height
      url
    }
    bigPic: profilePicture(size: 300) {
      width
      height
      url
    }
  }
}
レスポンスとしては、エイリアスとして付けた名前で返ってきます。
{
  "me": {
    "name": "Toshihiro Suzuki",
    "age": 31,
    "lilPic": {
      "width": 50,
      "height": 50,
      "url": "https://abc.com/50.jpg"
    },
    "bigPic": {
      "width": 300,
      "height": 300,
      "url": "https://abc.com/300.jpg"
    }
  }
}
次は、階層構造を表すクエリの例になります。
{
  me {
    name
    age
    friends {
      name
      age
    }
  }
}
このクエリは、ログインユーザの名前と年齢と、その友達の名前と年齢を取得するものになります。
レスポンスは以下です。
{
  "me": {
    "name": "Toshihiro Suzuki",
    "age": 31,
    "friends": [
      {
        "name": "Ryota Nishio",
        "age": 30,
      },
      {
        "name": "Ichiro Fukuda",
        "age": 31,
      },
      {
        "name": "Masahiro Yasuda",
        "age": 30,
      }
    ]
  }
}
このように、SQLではJoinを使う必要があるので多少複雑になってしまうクエリも、GraphQLでは比較的直感的に書くことができます。

最後にfragment機能を紹介します。
fragment機能はクエリの一部をfragmentとして切り出すことができる機能です。
{
  me {
    name
    age
    friends {
      name
      age
    }
  }
}
上記クエリをfragment機能によって以下のようにすることができます。
fragment userFragment on User {
  name
  age
}

{
  me {
    ...userFragment
    friends {
      ...userFragment
    }
  }
}
このように、name、ageなどの共通部分をfragmentとして切り出すことができます。

これ以外にも機能がたくさんありますので、ご興味のある方は参考文献[1]をご覧ください。

型システム

次にGraphQLの型システムについて説明します。

先程からクエリについて説明してきましたが、
本来GraphQLを使う場合には、まずスキーマを定義する必要があります。

以下が、これまで説明してきたクエリのスキーマになります。
type Query {
  me: User
  user(id: Int): User
}
type User {
  name: String
  age: Int
  profilePicture(size: Int = 50): ProfilePicture
  friends: [User]
}
type ProfilePicture {
  width: Int
  height: Int
  url: String
}
順を追って説明していきます。

まず、Queryは一番上位レベルの型で、先ほどのクエリでmeやuser(id: 3)と指定していた部分に当たります。userはidというint型のパラメータを受け取ります。これらの結果としてはそれぞれUser型が返ります。
次のUserは、名前(name)とプロフィール画像(ProfilePicture)と友達(friends)を持ちます。
それぞれ、String型、ProfilePicture型、User型という定義になっています。
最後にProfilePictureですが、幅(width)、高さ(height)、URL(url)をそれぞれint型、int型、String型で持つ定義となっています。

例えば以下のようなクエリがあったとします。
{
  me {
    name
    age
    profilePicture {
      width
      height
      url
    }
    friends {
      name
      age
    }
  }
}
meが返すのはUser型です。なので、そこで指定できるのはname, ageとprofilePictureとfriendsのどれかと決まります。
更に同様に、profilePictureはProfilePicture型で、friendsはUserのリスト型なので、指定できるものが決まりますね。
このように、スキーマを定義することで発行できるクエリを定義することができます。

ここで指定した型以外にも、Float型やBoolean型、列挙型やオブジェクト型などを指定することもできます。
更に詳細については参考文献[1]をご覧ください。

実際にGraphQLを動かしてみる

GraphQLはGraphQL.jsというリファレンス実装が用意されており、簡単に試すことが可能となっています。

まずは、npmでGraphQL.jsをインストールします。
$ npm install graphql
今回は、先程のスキーマを少し簡単にした以下のスキーマを定義してみましょう。
type Query {
  me: User
  user(id: Int): User
}
type User {
  name: String
  friends: [User]
}

こちらのソースコードは以下に貼りました。

https://gist.github.com/brfrn169/f8b8e57bb3a79540aace

簡単に解説すると、1~8行目で必要なものをimportしています。

https://gist.github.com/brfrn169/f8b8e57bb3a79540aace#file-graphql_example-js-L1-L8

10~36行目は今回使用するデータとなります。
今回はDBなどは用いず、メモリ上のデータに対してクエリを発行しています。

https://gist.github.com/brfrn169/f8b8e57bb3a79540aace#file-graphql_example-js-L10-L36

38~83行目は、本題のスキーマ定義のコードになります。
Queryのスキーマに対応するのは変数queryTypeに代入している部分で、Userのスキーマに対応するのは変数userTypeに代入している部分です。 ポイントはresolveです。ここに指定した関数の中で実際のデータを取得し返しています。

https://gist.github.com/brfrn169/f8b8e57bb3a79540aace#file-graphql_example-js-L38-L83

最後に85~124行目で、クエリを発行しています。
この例では以下の3つのクエリを発行してます。
{
  me {
    name
  }
}
{
  me {
    name
    friends {
      name
    }
  }
}
{
  user(id: "2") {
    name
    friends {
      name
    }
  }
}
その結果として以下のJsonが返ってきます。
{
  "me": "Toshihiro Suzuki"
} 
{
  "me": "Toshihiro Suzuki",
  "friends": [
    {
      "name": "Ichiro Fukuda"
    },
    {
      "name": "Ryota Nishio"
    }
  ]
}
{
  "user": "Ichiro Fukuda",
  "friends": [
    {
      "name": "Toshihiro Suzuki"
    }
  ]
}

https://gist.github.com/brfrn169/f8b8e57bb3a79540aace#file-graphql_example-js-L85-L124


イメージとしては、スキーマとそれに対するデータへのマッピングを指定していく感じでしょうか。
今回の例は非常に簡単なものでしたが、比較的理解しやすいのではないかと思います。

また、GraphQLでデータを取得するクエリと、データを操作するクエリを定義できます。
この例では、データを操作するミューテーション系のクエリについては触れませんでした。
ミューテーション系のクエリは以下を参考にすると良いでしょう。

https://github.com/RisingStack/graphql-server/blob/master/src/server/schema.js#L79-L142

まとめ

今回はGraphQLについて調べてみました。
クエリとそのレスポンスのJsonの構造が一致するというのはとても直感的で分かりやすいと思いました。
また、スキーマ定義も比較的直感的に書くことができたので、良いのではないかと思いました。
(今回の例が簡単すぎただけかもしれませんが、、)

今回は触れませんでしたが、GraphQLにはイントロスペクション機能がありスキーマの情報をGraphQLの文法で取得可能です。
こちらを用いることで、クエリのバリデーションやコードジェネレーション、IDEインテグレーションやドキュメントの自動生成などが可能となります。ご興味のある方はこちらも調べてみてはいかがでしょうか。

参考文献