GraphQL

10分で GraphQL 入門

これは 6/28 に開催される GraphQL ナイト のための資料です :information_desk_person:


スクリーンショット 2018-06-28 15.07.40.png


スクリーンショット 2018-06-28 15.08.09.png


スクリーンショット 2018-06-28 15.02.51.png


ここまでで分かること

  • クエリは独自言語みたい
  • クエリを変えることで柔軟にデータをとってこれそう
  • クエリと結果の見た目が似てるのは分かりやすそう(主観)

でも facebook はなんで作ったの?


facebook の事情

  • 数十億ユーザから膨大なリクエストがくる
  • 新興国の低速なネットワークからのモバイル接続も多い
  • アップデートされないモバイルアプリもサポートする

つまり

  • リクエスト回数は可能な限り減らしたい
  • 後方互換を維持しながら API を開発したい
  • 無駄なデータを送りたくない
  • (大規模開発なので型安全も欲しい)

規模は違えど我々も同じような問題を抱えているのでは? :thinking:


なぜ既存のものではダメだったのか?

より詳しくは昔の graphql.org のページを参照(internet archive

v.s. REST

  • 複雑なデータを取得しようとするとリクエスト回数が増える
  • 後方互換を保ちながら拡張すると送信するデータが増える
    • 後方非互換なバージョンが増えると管理が大変
  • 型が標準的にはついてこない

v.s. 専用 API

  • 巨大システムではメンテンナンスが大変
  • REST 同様古いクライアントを壊さないために注意が必要

GraphQL ではどうなっている?

  • GraphQL API そのものにはバージョンという概念はない。
  • クライアントが必要な field だけを返すので、新しい field を追加しても既存のクライアントへのレスポンスが変わらない
  • 1 つのクエリで複数のデータを取ってこれるので、何度も API リクエストする必要がない
  • 型が強制される

(field と型については割愛 :bow:)


image.png


よくあるかも知れない質問

Q. 独自言語の学習コストは?

A. :relaxed: ゼロではないが、SQL とかと比べれば屁でもない

Q. Node.js 使わないといけないの?

A. :no_good: そんなことはない

GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.
https://graphql.org

GraphQL = query language + runtime

仕様と実装は完全に切り離されており、主要な言語にはだいたい runtime がすでに存在する。(facebook 内で使われているのは Haskell 製っぽい)

Q. バックエンドはグラフデータベースじゃないといけない?

A. :no_good: そんなことはない

DB は何でもいいし、複数にまたがってもいい。例えば AWS AppSync では Amazon DynamoDB, Amazon Elasticsearch, AWS Lambda を横断して使うことができる。

Q. 採用して大丈夫?

A. :thinking: 悲惨な目にあう可能性は低い(が、言語によっては人柱感強いかも)

採用事例は増えているが、ツールの枯れ具合は言語毎に大きく異なる。エコシステムも REST などと比べるとまだまだ未成熟。

GraphQL の柔軟さゆえにいくらでも巨大なクエリを投げつけられるが、標準的な対処方法が存在しないという問題がある。個人的には公開 API にするのはちょっと怖い。

Q. DB 負荷が高いって聞いたけど?

A. :no_good: 正しく立ち向かえば基本的に大丈夫

何も考えずに実装するとそうなるが dataloader 的なツールを使って正しく実装すると、複数の REST API リクエストよりむしろ DB アクセス回数を減らせる可能性もある。 dataloader についても各種言語ごとに実装があるはず。


まとめ

  • GraphQL を作ったモチベーションは我々にも通じる
  • GraphQL は仕様と実装で構成される
  • エコシステムは発展途上
    • dataloader
    • Apollo
    • Relay
    • AWS AppSync
    • etc.
  • みんなで使って知見を(願わくば Qiita で)共有しよう

おまけ: なぜ GraphQL という名前なのか

(個人の見解です)

アプリケーションのデータはグラフとして解釈することができる

image.png

このグラフに対して以下のようなクエリを適用してみる。

query
query {
  # id が 1 の Article の
  article(id: 1) {
    # author の name と
    author { name }
    # likers の name をとってくる
    likers { name }
  }
}

image.png

response
{
  "data": {
    "article": {
      "author": {
        "name": "yuku"
      },
      "likers": [
        { "name": "htomine" },
        { "name": "tomoasleep" }
      ]
    }
  }
}

グラフを問い合わせるための言語だから Graph Query Language


おまけ2: Qiita での事例

GraphQL はクライアントからデータを取得することを念頭に置いたシステムだが、 Qiita ではサーバでのレンダリング時にも部分的に使っている。

https://gyazo.com/b0eb7d222524b0cc4df2b6d4ce44b480

サーバで作る初期データと「もっと読む」を押したときに JS で取得する JSON データの取得周りを DRY にしたい。

class HomeController < ApplicationController
  # `load_graphql_from_client` はファイルを読み込んで文字列として返すメソッド。
  TAG_FEED_QUERY = <<~GRAPHQL
    query {
      tagFeed(first: 20) {
        ...TagFeed
      }
    }
    #{load_graphql_from_client('TagFeedFragment')}
  GRAPHQL

  def tag_feed
    result = execute_graphql(TAG_FEED_QUERY)
    @tag_feed = result.data.tag_feed
  end
end
TagFeedFragment.graphql
fragment TagFeed on TagFeedConnection {
  # ...
}
app/views/home/tag_feed.html.slim
= tag.div(data: { props: @tag_feed })

この view が生成する HTML は以下のような形で

<div data-props="{JSON}"/>

{JSON} は TagFeedFragment.graphql によって定まる。apollo-codegen で TypeScript の型を生成できるので、安全に TypeScript から取り出すことが可能

import { TagFeedFragment } from "./types-generated-by-apollo-codegen"

const props: TagFeedFragment = JSON.parse(el.dataset.props)

また先程の fragment を使ってクライアントからサーバに問い合わせることができる

import TagFeed from "./graphql/TagFeedFragment"

const query = `
query ($after: String!) {
  tagFeed(after: $after, first: 20) {
    ...TagFeed
  }
}
${TagFeed}
`

const result = await post('/graphql', query, { after: "Mx==" })
const tagFeed: TagFeedFragment = result.data.tagFeed

こうして HTML に埋め込まれているデータと、クライアントから非同期に取得するデータが一致し、型もあって嬉しい