Edited at

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 さんです。