いよいよ始めるGraphQL

アジェンダ

  • GraphQLとは
  • GraphQL基本API
  • DEMO

RESTful APIの問題点

RESTful APIの問題点

  • 不要なデータを取ってくる(over-fetching)
  • テンプレートに足りないデータが存在する(under-fetching)
  • 複雑なデータに対応できない、複数エンドポイントへのリクエストが必要
  • 柔軟性がない(場合がある)
  • 直感的でない(場合がある)

スキーマファースト

  • バックエンド、フロントエンド共通のSchemeを設計する
  • 以後は独立して開発できる
  • (GraphQLの場合は)RESTより柔軟で、
    固有APIより定義をゆるくしたスキーマを定義する
    →グラフベースのスキーマ

GraphQL

GraphQL

  • http://graphql.org/
  • facebook製
  • query language
  • クエリを実行するためのサーバランタイム
  • 強い型付け
  • シングルエンドポイントで動作

GraphQLの実装

GraphQLのAPI

Query and Mutation

Query

  • データの取得
  • オブジェクトとそのフィールドを取得
  • 引数を取ったり、条件文をいれることも(または必須にする)も可能

fields (simple)

{
  posts {
    title,
    summary,
    created
  }
}
{
  "data": {
    "posts": [
      {
        "title": "new blog post",
        "summary": "this is new post",
        "created": "2017-3-13 20:12:03"
      },
      {
        "title": "second post",
        "summary": "this is second post",
        "created": "2017-3-14 13:22:35"
      },
      {...},
      {...}
    ]
  }
}

Query

Result

fields

  • クエリと結果は同じ形になる
  • サーバはクライアントが要求しているフィールドを正確に知っている必要がある(定義されているフィールドしか呼び出せない)

fields (nested)

{
  posts {
    title,
    summary,
    created,
    author {
      name,
      mail
    }
  }
}
{
  "data": {
    "posts": [
      {
        "title": "new blog post",
        "summary": "this is new post",
        "created": "2017-3-13 20:12:03",
        "author": {
          "name": "ben",
          "mail": "ben@blogger.com"
        }
      },
      {
        "title": "second post",
        "summary": "this is second post",
        "created": "2017-3-14 13:22:35",
        "author": {
          "name": "chris",
          "mail": "chris@blogger.com"
        }
      }
    ]
  }
}

Query

Result

fields

  • オブジェクトの内部をさらに参照するクエリ
  • RESTだと複数回リクエストが必要な処理も、GraphQLなら1度のリクエストで呼び出し可能

Arguments

  • Queryに対して引数をつけることができる
  • オプション or 必須
{
  posts(count: 1) {
    title,
    summary,
    created
  }
}
{
  "data": {
    "posts": [
      {
        "title": "new blog post",
        "summary": "this is new post",
        "created": "2017-3-13 20:12:03"
      }
    ]
  }
}

Query

Result

Arguments

  • 引数をフィールドに渡すことも可能
  • ENUMを渡して、サーバで変換処理を行うこともできる
{
  posts(count: 1) {
    title,
    summary,
    created(format: DATE)
  }
}
{
  "data": {
    "posts": [
      {
        "title": "new blog post",
        "summary": "this is new post",
        "created": "2017/3/13"
      }
    ]
  }
}

Query

Result

Aliases

  • 取得したデータに対して別名をつけることが可能
  • 同じフィールドを別のデータとして取得したい場合などに利用
{
  posts {
    title
  },

  authorNames: authors {
    name
  },

  authorMails: authors {
    mail
  }
}
{
  "data": {
    "posts": [
      {"title": "new blog post"},
      {"title": "second post"}
    ],
    "authorNames": [
      {"name": "ben"},
      {"name": "chris"}
    ],
    "authorMails": [
      {"mail": "ben@blogger.com"},
      {"mail": "chris@blogger.com"}
    ],
  }
}

Query

Result

Fragments

  • 再利用可能なユニットを定義し、クエリに含めることができる
  • 複雑なデータを(UIコンポーネントベースなど)分割して定義することでクエリを容易にするのが目的
  • fragment [fragmentName] on Type {} で定義
  • ...[fragmentName] で呼び出し

Fragments

{
  jan: posts(month: 1) {
    ...blogData
  },

  feb: posts(month: 2) {
    ...blogData
  },

  mar: posts(month: 3) {
    ...blogData
  }
}

fragment blogData on Posts {
  title,
  body,
  author {
    name
  }
}
{
  "data": {
    "jan": [{
      "title": "January post",
      "body: "...",
      "author": {
        "name": "ben"
      }
    }],
    "feb": [{
      "title": "February post",
      "body: "...",
      "author": {
        "name": "chris"
      }
    }],
    "mar": [{
      "title": "March post",
      "body: "...",
      "author": {
        "name": "ben"
      }
    }],
  }
}

Query

Result

Variables

  • クエリの外側から(動的な)値を渡す方法
  • queryを使って関数のようなものを定義

  • 引数として$を接頭辞にした引数(と、その型)を定義

  • queryの中にある$xxxへ引数が渡される。

  • variablesはキーバリュー型となる

Variables

query getPosts($count: Int!){
  posts(count: $count) {
    title,
    summary,
    created
  }
}
{
  "data": {
    "posts": [
      {
        "title": "new blog post",
        "summary": "this is new post",
        "created": "2017-3-13 20:12:03"
      },
      {...},
      {...}
    ]
  }
}

Query

Result

Variables

{
  "count": 3
}

Variablesの型

  • 先頭が$付きの変数で、コロンを挟んで型が入る
  • 型はスカラー型、Enum、オブジェクト型のいずれか

Operation name

getPosts の部分

  • プログラムの関数名のように名前をつけることができる
query getPosts($count: Int!){
  posts(count: $count) {
    title,
    summary,
    created
  }
}

Directives

  • Variablesなどを用いて、動的にクエリのFieldsを減らしたり、増やしたりする方法

  • @include(if: Boolean) でifがtrueのときフィールドを追加

  • @skip(if: Boolean)  ifがtrueのときフィールドを除去

query getPosts($noSummary: Boolean){
  posts {
    title,
    summary @skip(if: $noSummary),
    created
  }
}

Inline Fragments

  • オブジェクト型によって出力するフィールドを変更する方法
query getComment {
  comments {
    name,
    comment,
    ... on FacebookUser {
      realName
    }
    ... on TwitterUser {
      account
    }
  }
}
{
  "data": {
    "comments": [
      {
        "name": "Brett",
        "comment": "...",
        "realName": "Brett Reed"
      },
      {
        "name": "Matt",
        "comment": "...",
        "account": "@matt"
      }
    ]
  }
}

Query

Result

Mutation

  • データの更新・作成・削除
  • 更新後のデータを受け取る

Mutation

  • mutationキーワードを指定
  • サーバで処理された後の結果のフィールドを所得
mutation {
  createAuthor(
    name: "tim",
    mail: "tim@blogger.com"
  ) {
    _id
    name
  }
}
{
  "data": {
    "createAuthor": {
      "_id": "tim",
      "name": "tim"
    }
  }
}

Query

Result

Mutation

  • 複数のmutationも可能
  • 処理はシリアルで実行される
mutation {
  "tim": createAuthor(
    name: "tim",
    mail: "tim@blogger.com"
  ) {
    _id
  }

  "fred": createAuthor(
    name: "fred",
    mail: "fred@blogger.com"
  ) {
    _id
  }

}
{
  "data": {
    "tim": {
      "_id": "tim"
    }
    "fred": {
      "_id": "fred"
    }
  }
}

Query

Result

Schemas and Types

Schemas and Types

  • どのオブジェクトがどのフィールド・型を持っているか
  • GraphQL schema languageという共通の仕様
  • いわゆるモデルのようなもの

GraphQL Object Type と Field

type Post {
  title: String!
  summary: String
  body: String
  created: String
  author: Author
}

Scheme

実際のSchemaの定義

GraphQL Object Type と Field

  • PostGraphQL Object Typeでいわれるオブジェクト型、幾つかのフィールドを持つことができる。

  • title, summary, body, created, author はフィールド

  • StringはGraphQLで定義できる、スカラー型のひとつ。!をつけることでnon-nullableであることを示す

  • [Episode]は Episode型の配列であることを定義。上記同様に!はnon-nullableを示す。ただこの場合は0個またはそれ以上の配列となる

Arguments

type BlogSchema {
  posts(count: Int! = 10): [Post]
}

Scheme

Arguments

  • GraphQLオブジェクト型のすべてのフィールドは引数を持つことができる。
  • 引数にはすべて名前をつける必要がある
    必須でない場合はオプションとしてデフォルト値も定義できる
  • 前項の場合はcountがない場合は10がデフォルト値として利用される

QueryとMutation型

  • スキーマの大本にはQueryMutationが2つある(最上位はこれしかない)
  • Queryはすべてのスキーマが持ち、
    Mutationはなくてもよい
{
  type Query {
  ...
  }

  type Mutation {
  ...
  }
}

Types

Scala types

  • スカラ型には以下の種類がある
    Int, String, Float, String, Boolean, ID
  • ID... オブジェクトを再フェッチするときや、キャッシュのためのキーとして利用される文字列

Custom Scala types

  • 独自の方を定義
  • 以下は、カスタムスカラ型として、Date型を定義した例
    (内容は実装によって異なる)
scalar Date

Enum

  • 値が指定した値のいずれかであることを定義した型
enum Category {
  JAVA
  JAVASCRIPT
  NODEJS
  PYTHON
}

List and requirement

  • []で括ることにより配列として定義できる
  • !で必須として定義できる
type Post {
  title: String!
  comments: [Comment]!
}

Interface

  • 共通のschemeに対しインターフェイスを定義することも可能
  • Interface、implements キーワードを使って定義
interface HasAuthor {
  author: Author
}

type Comment implements HasAuthor {
  comment: String
}

type Post implements HasAuthor {
  title: String
  body: String
}

Other types...

  • Union types
    • 複数のオブジェクト型を跨いで値を返す?
  • Input types
    • mutationなどで利用する複雑なオブジェクト型を定義する

実装とDemo

インストール

$ npm i express graphql body-parser --save
// index.js
const express = require('express');

const app  = express();
const PORT = 3000;

const server = app.listen(PORT, function () {
  const host = server.address().address;
  const port = server.address().port;
  console.log('listening at http://%s:%s', host, port);
});

index.js

$ node index.js
const graphql = require('graphql');
const { GraphQLSchema, GraphQLObjectType, GraphQLString } = graphql;

const Query = new GraphQLObjectType({
  name: 'baseSchema',
  fields : {
    ping: {
      type: GraphQLString,
      resolve: () => 'pong'
    }
  }
});

// const Mutation = new GraphQLObjectType({...});

const schema = new GraphQLSchema({
  query: Query
  //, mutation: Mutation
});

module.exports = schema;

schema.js

スキーマを定義

const graphql = require('graphql');
const bodyParser = require('body-parser');
const schema = require('./schema.js');

// POSTのbodyをtextとしてパース
app.use(bodyParser.text({ type: 'application/graphql' }));

app.post('/graphql', (req, res) => {
  // GraphQLを実行
  graphql.graphql(schema, req.body).then((result) => {
    res.send(JSON.stringify(result, null, 2));
  });
});

リクエストされたqueryを処理 (index.js)

$ curl -XPOST -H "Content-Type:application/graphql" \
    -d "query baseSchema { ping }" http://localhost:3000/graphql

チェック

{
  "data": {
    "ping": "pong"
  }
}

便利なライブラリ

続きは

まとめ

  • メリット
    • Just in Timeなデータを取得できる

    • クライアント側(UI側)の変更に対して、サーバ側でAPIを変えなくてもいい

    • クライアントが定義情報を知っているから、バージョン管理は(あんまり)しなくてもいい

    • APIドキュメントも自動生成できる

まとめ

  • デメリット
    • 学習コスト(特にサーバ側の!)

    • どのくらいの規模に耐えうるか?(複雑なスキーマが発生してメンテナンス性が下がったりするか)

    • 開発パラダイムのシフト(特にサーバ側の!)

その他

END

いよいよ始めるGraphQL

By Kyohei Rampage Tsukuda

いよいよ始めるGraphQL

  • 1,606