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

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

---

はじめまして。Retty Web チームでインターンをしている宇野です。
インターンでは主に、オウンドメディアの[Rettyグルメニュース](https://retty.news/)(以下、グルメニュース)の開発をしています。今回の記事では、グルメニュースに 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://qiita-image-store.s3.amazonaws.com/0/142436/e2fda98b-d336-dea4-c43d-2b7a8e3e2870.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](https://github.com/nuwave/lighthouse) を使用します。Lighthouse はディレクティブ(`@`)を使って、モデルのリレーションやバリデーションを定義できます

**Lighthouse のインストール**

```bash

$ composer require nuwave/lighthouse
```

**config ファイルの配置**

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

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

```bash
composer require mll-lab/laravel-graphql-playground
```

<img width="1278" alt="c531070a-a770-2b77-9cac-c976a2b31b89.png" src="https://qiita-image-store.s3.amazonaws.com/0/142436/ee42414f-0f82-c44c-8cb8-fcf56e0e51f6.png">


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

- shops テーブル

| カラム名 | 型 |
| :-------: | :----:|
| id | int|
| name | string |
| address | string |

- items テーブル

| カラム名 | 型 |
| :-------: | :----:|
| id | int|
| name | string |
| genre | string |

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

```text: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")
}
```

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

**DOCS**

<img width="580" alt="4857071f-2d61-4dbf-4223-3889b54b0a50.png" src="https://qiita-image-store.s3.amazonaws.com/0/142436/fdbfe4f3-fced-bac0-b3a3-048dd86ec985.png">

**SCHEMA**

<img width="279" alt="3d0ec0a7-48cb-f283-b9a0-69f033f2c1b4.png" src="https://qiita-image-store.s3.amazonaws.com/0/142436/191aa1c5-8e3f-0c7c-fe0a-e54f74dc0878.png">

フロント側では、既にどちらも `id` と `name` は持っており、 `address` と `genre` が欲しいとします。

```text:クエリ
query {
  shops(id: 2, count: 3, page: 1) {
     data {
      address
    }
  }
  items(genre: 1, count: 3, page: 1) {
    data {
      id,
      genre
    }
  }
}
```

```text:実行結果
{
  "data": {
    "shops": {
      "data": [
        {
          "address": "92394 Armando Shores Suite 623\nDeckowshire, NV 22748"
        }
      ]
    },
    "items": {
      "data": [
        {
          "id": "4",
          "genre": 1
        },
        {
          "id": "6",
          "genre": 1
        }
      ]
    }
  }
}
```

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

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

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

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