Apolloでの綺麗なAPI実装（GraphQL）を試す

# はじめに
今回のサンプルは以下に用意しました。
[Apollo Server & Apollo Clientサンプル](https://github.com/teradonburi/ApolloExample)

以前こんな記事を書きました。
[React(+Redux)+gRPCで実現するクリーンアーキテクチャ＋マイクロサービス構成](https://qiita.com/teradonburi/items/ec18e6517afbc8b6b6e2)
BFFのフロントエンドAPI部分に関して、次の記事を見てクライアント通信の部分をGraphQLで実装できるとより柔軟で堅牢な気がしたので試してみました。
[世のフロントエンドエンジニアにApollo Clientを布教したい](https://qiita.com/seya/items/26c8a0dc549a10efcdf8)

REST API、 GraphQLの違いに関しては下記を参考にしてください
[アプリ開発の流れを変える「GraphQL」はRESTとどう違うのか比較してみた](https://www.webprofessional.jp/rest-2-0-graphql/)

GraphQLを導入するメリットはざっくり以下の通りです。

- APIのインタフェース定義(送信パラメータのデータ型定義)ができる（それにともない、データの型定義によるAPIパラメータのデータ型バリデーションチェックができる）
- データ取得条件に合わせてGETエンドポイントを大量に作成しなくて済む（GraphQLスキーマ単位になる）
- APIのバージョン管理ができる
- APIレスポンスのキャッシュが容易

反面デメリットとしては以下があります。

- 日本語の記事が少ない
- GraphQLインタフェース定義を学習するコストがかかる

Apollo Clientに関しては通信後のデータ管理を[Link](https://www.apollographql.com/docs/link/overview.html)という機能で保持する仕組みも持っているため、Reduxの代替としても期待されています。

# ApolloでのGraphQL導入
[Apollo](https://www.apollographql.com/)はGraphQLのフロントエンド＆バックエンドのライブラリです。
バックエンド側はApollo Server、フロントエンド側は Apollo Clientを導入する必要があります。
またGraphiQLというVisual Editorがツールとして付属しているのでAPIの動作確認を簡単に行うことができます。

## バックエンド（Apollo Server）

各種NodeJSフレームワークに対応しています。

- express
- koa
- hapi
- restify
- lambda
- micro
- azure-functions
- adonis

今回はexpressで実装するのでexpress対応のapolloをnpmインストールします。

```
npm install --save apollo-server-express graphql-tools graphql express body-parser
```

次の実装が最小サンプルとなります。

```server.js
const express = require('express')
const bodyParser = require('body-parser')
const { graphqlExpress, graphiqlExpress } = require('apollo-server-express')
const { makeExecutableSchema } = require('graphql-tools')
const app = express()

process.on('uncaughtException', (err) => console.error(err))
process.on('unhandledRejection', (err) => console.error(err))

app.use(bodyParser.urlencoded({extended: true}))
app.use(bodyParser.json())


// GraphQLスキーマ定義
const typeDefs = `
  """
  type Query　（必須）
  """
  type Query { books: [Book] }

  """
  返却するデータ構造
  """
  type Book { title: String, author: String }
`

// ダミーデータ
const books = [
  {
    title: 'Harry Potter and the Sorcerer\'s stone',
    author: 'J.K. Rowling',
  },
  {
    title: 'Jurassic Park',
    author: 'Michael Crichton',
  },
]

// resolvers
const resolvers = {
  Query: { books: () => books },
}

// スキーマ生成
const schema = makeExecutableSchema({
  typeDefs,
  resolvers,
})

// GraphQLエンドポイント
app.use('/graphql', bodyParser.json(), graphqlExpress({ schema }))

// GraphiQL：GraphQLクエリのvisual editor
// TODO: 本番デプロイ時はアクセス出来ないようにする
app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }))

app.listen(5000, () => {
  console.log('Access to http://localhost:5000')
})
```

次コマンドで実行

```
$ node server.js
```

`http://localhost:5000/graphiql`にアクセスすると
GraphiQLが表示されます。
次のようにクエリを書いて▶ボタンで実行するとダミーデータが取得できます。

```
query {
  books {
      title,
      author,
  }
}
```

<img width="948" alt="スクリーンショット 2018-06-10 15.07.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/55077/ebc73548-767d-9504-88be-8263b195d7c6.png">


### GraphQLのスキーマ定義

細かい詳細は下記記事がまとまっています。
[GraphQL入門 - 使いたくなるGraphQL](https://qiita.com/bananaumai/items/3eb77a67102f53e8a1ad)

データの取得にはQuery、データの更新にはMutationを使います。
スキーマ定義に`type Query`もしくは`type Mutation`の定義は必須です。
スキーマ定義は次のように行います。

```server.js
const typeDefs = `

  type Query {
    フィールド名(引数): 返却データ型
  }

  type Mutation {
    フィールド名(引数): 返却データ型
  }

}
`

// resolvers
const resolvers = {
  Query: {
    フィールド名: (引数) => 返却データ,
  },
  Mutation: {
    フィールド名: (引数) => 返却データ,
  },
}
```

データの基本型は次のようになっています。

- Int: 32bit整数型
- Float: 浮動小数型
- String: UTF-8 文字列型
- Boolean: true もしくは false
- ID: ユニークなスカラー値、キャッシュに使われる。Stringをシリアライズ（直列化）したデータで保存されている

加えて任意のオブジェクト単位にデータ型を定義できます。
次の例はBook型を定義した例です。
なお、パラメータを必須にしたい場合はデータ型末尾に`!`をつけます。

```server.js
type Query { 
  books: [Book]!,
}

type Book { title: String, author: String }
```

さらに詳細はGraphQL公式：[Schemas and Types](https://graphql.org/learn/schema/)を参考にしてください。

複数メソッドを定義するときは`type Query`もしくは`type Mutation`のブロック内にメソッドを追加します。
ちなみに`"""`で囲めばコメントになります。

```server.js
const typeDefs = `
  type Query { 
    books: [Book],
    items: [Item],
  }

  """
  返却するデータ構造
  """
  type Book { title: String, author: String }
  type Item { title: String }
`

// resolvers
const resolvers = {
  Query: {
    books: () => books,
    items: () => items,
  },
}
```

引数を取る場合はつぎのように書きます。
[Resolver function signature](https://www.apollographql.com/docs/graphql-tools/resolvers.html#Resolver-function-signature)
argsにパラメータが渡ってきます。

```server.js

// GraphQLスキーマ定義
const typeDefs = `
  type Query { 
    books(author: String): [Book],
  }

  type Book { title: String, author: String }
`

// resolvers
const resolvers = {
  Query: {
    books: (obj, args, context, info) => {
      const author = args.author
      return books.filter(book => book.author === author)
    },
  },
}

```

GraphiQLにて次のようなクエリを作成します。

```
query ($author: String!){
  books(author: $author) {
      title,
      author,
  }
}
```

QUERY VARIABLESには次の検索条件パラメータを指定
JSONのキーが$authorに入ります。

```
{
  "author": "Michael Crichton"
}
```

実行すると指定した条件の検索ができました
<img width="1139" alt="スクリーンショット 2018-06-10 19.01.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/55077/c8342508-7752-2a60-6b10-4ca4a3859994.png">

## フロントエンド（Apollo Client）
React + Apollo ClientからApollo Serverに通信を試みます。
まず、Apollo Clientをダウンロードします。

```
npm install --save apollo-boost react-apollo graphql-tag graphql
```

ApolloClientを生成して、ApolloProvider経由でアプリケーションコンポーネント`<App />`をwrapすることで`<App />`以下でgraphQLが使えるようになります。

```index.jsx
/*globals module: false */
import React  from 'react'
import ReactDOM from 'react-dom'
import { hot } from 'react-hot-loader'
import { ApolloProvider } from 'react-apollo'
import ApolloClient from 'apollo-boost'
import App from './App'

const client = new ApolloClient({
  uri: 'http://localhost:5050/graphql',
})


const render = () => {
  ReactDOM.render(
    <ApolloProvider client={client}>
      <App />
    </ApolloProvider>
    ,
    document.getElementById('root'),
  )
}

// Webpack Hot Module Replacement API
hot(module)(render)

render()
```

App.jsです。qqlにクエリを記述します。
Queryコンポーネントで実際のAPIコールを行います。
API結果は`{loading, error, data}`は返却されます。
使いやすいようにgraphQLというHOCを作成してwrapしてみました。

```App.jsx
import React from 'react'
import gql from 'graphql-tag'
import { Query } from 'react-apollo'

// GraphQLクエリ
const query = gql`
query ($author: String!){
	books(author: $author) {
    	title,
      author,
  }
}
`

// HOC
const graphQL = (param) => (WrappedComponent) => (props) => (
  <Query
    query={query}
    variables={param}>
    {({loading, error, data}) => <WrappedComponent {...props} loading={loading} error={error} data={data} />}
  </Query>
)


class App extends React.Component {

  render () {
    const { loading, error, data } = this.props
    if (loading) return <p>Loading...</p>
    if (error) return <p>Error...</p>

    return (<div>
      {data.books.map(book =>
        <div key={book.title}>
          <h4>{book.title}</h4>
          <span>{book.author}</span>
        </div>
      )}
    </div>)
  }
}

export default graphQL({
  author: 'Michael Crichton',
})(App)
```

実行するとLoading表示からAPI取得後、データが表示されます。
<img width="255" alt="スクリーンショット 2018-06-11 4.30.16.png" src="https://qiita-image-store.s3.amazonaws.com/0/55077/3627d07b-70b9-3234-8e73-9ef64913d846.png">

Reduxのreducerとreact-reduxのconnectの置き換えみたいなことができました。

参考：[Apollo Client + React 入門](https://qiita.com/seya/items/e1d8e77352239c4c4897)


# Apollo ClientでReduxの代用ができるのか
参考記事内で[コラム: GraphQLはReduxを置き換えるのか](https://qiita.com/seya/items/26c8a0dc549a10efcdf8#%E3%82%B3%E3%83%A9%E3%83%A0-graphql%E3%81%AFredux%E3%82%92%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88%E3%82%8B%E3%81%AE%E3%81%8B)とあったのですが、
Apollo ClientはGraphQLでの通信結果の状態管理は[SSR](https://www.apollographql.com/docs/react/features/server-side-rendering.html#server-rendering)などを含めて通信周り(redux-thunkやredux-saga)の結果をReduxに入れてる箇所に関しては置き換えできそうでした。(主にreact-reduxのconnectとreducer周り)
~~逆に通信以外のアプリケーションデータに関して（例えばreact-router-reduxのような画面遷移状態をReduxに保持する等）を管理する機構が貧弱な（というか存在しない？）気がしました。(apollo-link-stateは通信周りだけっぽいですし・・・)
上記の事から、現状だとReduxを捨てることはできず、ローカルのStoreが２つ必要になるのでアンチパターンな気がしました。（逆にそこさえ解決できれば積極的に使いたい感）~~
[Routing](https://www.howtographql.com/react-apollo/4-routing/)を見るとpropsにhistory渡せているっぽい？
GraphQLの設計思想自体はとても良いものなので通信周りはRest APIよりもスマートになると思いました。