laravel
GraphQL

GraphQL でフロントに優しい API を作ろう

これはRetty Inc. AdventCalendar2018 21日目の記事です。
昨日は @takumi-suzuki さんの リモート環境をFire HDに切り替えてみた でした。
FireHD が悲しい結末を迎えてしまいましたね。:cry:


はじめまして。Retty Web チームでインターンをしている宇野です。
インターンでは主に、オウンドメディアのRettyグルメニュース(以下、グルメニュース)の開発をしています。今回の記事では、グルメニュースに GraphQL を導入することにしたので、実際に触ってみたいと思います。

はじめに

最初に、GraphQL 導入の経緯を説明します。
グルメニュースには現在、記事を配信する側と入稿する側のサービスが別々に存在します。2つのサービスがあることにより、メンテナンスコストが倍になってしまい手間です。 :worried:
そこで、API を作り一つのサービスにして、サーバーとフロントを分離しようとしています。この API 部分に GraphQL を利用します。

GraphQL とは?

Facebook が開発した、API のためのクエリ言語です。クライアントとサーバーのやりとりを簡単にするために使用されます。
例えば、User 情報を取得する場合 GraphQL では次のように書きます。

type User {
  id: ID
  name: String
}

type Query {
  user: User
}

GraphQL はクエリ言語なので特定の DB やストレージに結びついておらず、RDB だけではなく localStorage や REST API に対しても 使うことができます。

RESTful API との違いは?

RESTful API との違いを次の図で説明していきます。

f3670b34-1b4b-9942-0fa6-409c4118cf0e.png

https://fullstackmark.com/post/17/building-a-graphql-api-with-aspnet-core-2-and-entity-framework-core

RESTful は、ルーティングやスキーマの定義が自由なため、フロント側と並行して開発するのが難しいです。しかし、GraphQL だとエンドポイントは一つで、スキーマの定義もモデルの設計をしていれば問題なくできます。他にも、次のような pros/cons があります。

pros

  • API 通信が1回で済む
    • リソース毎に API と通信しない
  • リソースの差分だけを指定して取得できる
    • 既にページで持ってる情報を改めて取得しなくて良い
  • JSONに似た構文で書きやすい

cons

  • キャッシュが URL ベースでできない
  • モニタリングが難しい
    • エンドポイント毎の監視できない

GraphQL の導入

それでは、 GraphQL を使ってみましょう。グルメニュースは、PHP フレームワークの Laravel を使っています。
GraphQL のフレームワークにはlaravel のパッケージである Lighthouse を使用します。Lighthouse はディレクティブ(@)を使って、モデルのリレーションやバリデーションを定義できます

Lighthouse のインストール

$ composer require nuwave/lighthouse

config ファイルの配置

php artisan vendor:publish --provider="Nuwave\Lighthouse\Providers\LighthouseServiceProvider" --tag=config

これで GraphQL を使い始めることはできますが、開発に便利なパッケージ laravel-graphql-playground も一緒に入れましょう(以下、playground)。playground は、 ブラウザ で GraphQL のサーバにクエリを投げて結果を確認できるパッケージです。また、右側にあるタブ DOCSSCHEMA で Lighthouse が生成したスキーマファイルを見ることができます。

composer require mll-lab/laravel-graphql-playground

c531070a-a770-2b77-9cac-c976a2b31b89.png

Lighthouse ファイルの作成

ファイルは routes/graphql 以下に配置します。次の2つモデルを作成して、 playground でデータを取得してみます。

  • shops テーブル
カラム名
id int
name string
address string
  • items テーブル
カラム名
id int
name string
genre string

表を元にスキーマはこのようにしました。

schema.graphql
type Shop {
  id: ID!
  name: String
  address: String
  items: Item @hasMany
}

type Item {
  id: ID!
  name: String
  genre: Int
  shop: Shop @belongsTo
}

type Query {
  shops(
    id: ID @eq
    name: String @where(operator: "like")
    address: String @where(operator: "like")
  ): [Shop] @paginate(model: "Shop")

  items(id: ID @eq
    name: String @where(operator: "like")
    genre: Int @eq
  ): [Item] @paginate(model: "Item")
}

DOCSSCHEMA タブの内容は次のようになります。(一部分のみ)

DOCS

4857071f-2d61-4dbf-4223-3889b54b0a50.png

SCHEMA

3d0ec0a7-48cb-f283-b9a0-69f033f2c1b4.png

フロント側では、既にどちらも idname は持っており、 addressgenre が欲しいとします。

クエリ
query {
  shops(id: 2, count: 3, page: 1) {
     data {
      address
    }
  }
  items(genre: 1, count: 3, page: 1) {
    data {
      id,
      genre
    }
  }
}
実行結果
{
  "data": {
    "shops": {
      "data": [
        {
          "address": "92394 Armando Shores Suite 623\nDeckowshire, NV 22748"
        }
      ]
    },
    "items": {
      "data": [
        {
          "id": "4",
          "genre": 1
        },
        {
          "id": "6",
          "genre": 1
        }
      ]
    }
  }
}

shopitem の異なるリソースを一回の通信で取ってくることができ、 address, genre だけを取得することができました。 :smile:

まとめ

GraphQL 導入の説明と Lighthouse を使って GraphQL に触ってみました。キャッシュやモニタリングなど難しい部分もあり、技術的に枯れていないので学習コストは高いかもですが、銀の弾丸はないんです。

まだまだ導入事例は少ない GraphQL ですが、とても便利なのでぜひ使ってみてください。

明日は @yongyu-li さんです。