スキーマからコード生成してハッピーになる

GraphQL Advent Calendarの16日目です。
この記事ではGraphQLのスキーマから型ファイルを生成するツールである[graphql-code-generator](https://graphql-code-generator.com/)を紹介します。
似たようなツールである[graphqlgen](https://oss.prisma.io/graphqlgen/)との違いも簡単に説明します。

# TL;DR
* GraphQLスキーマとは別にスキーマに対応したモデルやリゾルバのシグネチャなどのファイルを用意するのは冗長な作業でなんとかしたい 
* [graphql-code-generator](https://graphql-code-generator.com/)を使えばGraphQLスキーマに対応したTypescriptなどの言語の定義ファイルを生成してくれる
* プラグインが豊富でサーバーサイドだけでなくreact-apolloなどに対応したコードの生成も可能
* prismaの[graphqlgen](https://github.com/prisma/graphqlgen)もあるけど機能的には[graphql-code-generator](https://graphql-code-generator.com/)のほうが充実している
* リゾルバーだけならgraphqgenで十分

# 背景

GraphQLにはGraphQL SDL (Schema Definition Language)とよばれるAPIを定義するための独自の言語があります。
もちろんSDLを書くだけでは不十分で「このフィールドに対してはこの処理をしてこの値を返す」というような
リゾルバーを実装する必要があります。実装に際してスキーマの型に対応したTypescriptの型を定義したり、
リゾルバーのシグネチャを定義するのですが、**スキーマとTypescriptでの型定義とで大分重複するところがあり (というかほぼ同じ)、なんか冗長な作業だなと思っていました。**
なんかいいツール無いかなと調べて出会ったのがgraphql-code-generatorです。
今すぐ試してみたい方は[ライブデモ](https://graphql-code-generator.com/#live-demo)をどうぞ :smile: 

# graphql-code-generatorとは
GraphQLスキーマからTypescriptの定義ファイルを生成してくれるツールです。[^1]
[^1]: プラグインを作成可能なので他の言語も対応可能です。
Golangでは似たツールとして[gqlgen](https://gqlgen.com/)があります。
このツールには以下の特徴があります。

* スキーマファースト  
  スキーマを元にコードを生成するので、実装言語での冗長な作業が防げる。
* 型安全  
  リゾルバーの引数なども全て型を与えるので、打ち間違えなどで存在しないフィールドにアクセスしてしまうみたいな問題を防げる

# graphql-code-generatorの仕組み
graphql-code-generatorはデフォルトで`codegen.yml`と`codegen.json`[^2]という設定ファイルを読みに行き
[^2]: 設定で変更可能です。
どのパイプラインで各ファイルを生成するかを決めます。
例えば以下の`codegen.yml`では、スキーマ`./schema.graphql`から`./server.ts`と`./resolver.ts`を生成する設定をしています。
各ファイルの生成パイプラインは`plugins`以下に列挙して指定します。
公式にも書かれているとおり、プラグインごとにスキーマが独立して処理され、各プラグインの出力が単純に上から順番にconcatenateされるだけです。
これは何を意味するかというと、各ファイルでプラグインを共有していたとしても(以下の例だと`typescript-common`)、重複するコードが各ファイルに出力されてしまうということです。
後ほど説明する`add`というプラグインを使って共通なコードを`import`するコードをインジェクトするのが解決策の一つかなと思います。

```yaml:codegen.yml
schema: ./schema.graphql
generates:
  ./server.ts:
    plugins:
      - add: "/* コメントなど任意のコードを追加できます。 */"
      - time
      - typescript-common
      - typescript-server
  ./resolver.ts:
    plugins:
      - typescript-common
      - typescript-resolvers
```

上の`./codegen.yml`で使われているプラグインの説明を簡単にしておきます。

* add  
  任意の文字列を出力する。
  tslintをコメントで設定したりだとか、サーバー側とクライアント側で共有したいコードを`import`したりしたいときに使えそうです。
* time  
  出力時の時間を出力する。
* typescript-common
  列挙体、スカラー、入力型のようなサーバー側とクライアント側で共通して使うようなものを出力する。
* typescript-resolvers  
  サーバーサイドのリゾルバーのシグネチャの出力する。
* typescript-server  
  サーバーが使うコード (types, directives, interfaces, unions) を出力する。

他にもqueryやmutationなどのドキュメンのからクライアント側が使うコードを出力するプラグインや、
react-apolloのコンポーネントを出力するものなどの色々あります。
詳しくは[公式ページ](https://graphql-code-generator.com/docs/plugins/)をご覧になってください。

# 実際に使ってみる

まずはpeerDependencyである`graphql`と`graphql-code-generator`を入れましょう。
次に必要なプラグインもインストールしちゃいます。ここでは上で紹介したプラグインのみを入れます。
(以下では`yarn`を使っていますが、`npm`を使ってもいいと思います。)

```bash
yarn add -D graphql graphql-code-generator
yarn add -D graphql-codegen-add graphql-codegen-time graphql-codegen-typescript-common graphql-codegen-typescript-resolvers graphql-codegen-typescript-server
```

次に`codegen.yml`をプロジェクトのルートに記述します。今回は上述のものを使います。

次にスキーマファイルを用意します。

```graphql:schema.graphql
schema {
  query: Query
}

type Query {
  user(id: ID!): User
}

enum Role {
  USER
  ADMIN
}

type User {
  id: ID!
  username: String!
  email: String!
  role: Role!
}
```

あとはgraphql-code-generatorのスクリプトである`gql-gen`を実行するだけなのですが、
グローバルにインストールしていないので`package.json`にスクリプトを追加します。

```json:package.json
 ...
 "scripts": {
    ...
    "gen": "gql-gen",
    ...
 },
 ...
```


これでコードを生成する準備ができましたので生成してみましょう！

```bash
yarn gen
```

プロジェクトルートに`server.ts`と`resolver.ts`ができていると思います。
中身の詳細は説明しませんが、主に以下の内容が出力されています。

* Roleに対応する列挙体
* リゾルバーのシグネチャ
* オブジェクトの型(`Query`と`User`)
* `user(id: ID!): User`の引数の型

あとはこのファイルを別ファイルでインポートして使うだけです。

# graphqlgenとの違い
似たようなツールとしてPrismaの[graphqlgen](https://github.com/prisma/graphqlgen)があります。
graphqlgenの[GitHubのIssue](https://github.com/prisma/graphqlgen/issues/152)にGraphQL Code Generatorの著者の方が違いはどこにあるのかという質問をしています。
graphqlgenの著者の回答を簡単にまとめると、
**graphqlgenはJavascriptベースの型言語で、サーバーサイドのGraphQLリゾルバーのコード生成に特化しているのに対して、graphql-code-generatorはより一般的なツールである**ということです。
具体的には、

* フロントエンドとバックエンドのスコープ
  * graphqlgenはサーバーサイドのリゾルバー生成に特化しているので、モデルなどは自分で定義する必要がある
  * graphql-code-generatorはreact-apolloなどフロントエンドも網羅している
* サポートするプログラミング言語
  graphqlgenはTypescript, FlowなどのJavascriptのエコシステムに特化している。
    これに対して、graphql-code-generatorの作者はgraphql-code-generatorも元々Typescriptファーストで作られており、サポートをする言語の汎化を行っているとは一切言っていないと反論しています。
    確かにドキュメントを見た感じでは生成されたファイルはすべてがTypescriptとなっているのでこの主張はよくわかりません。
    ただ、graphql-code-generatorはプラグインを自前で用意することで基本的にTypescript以外の言語に出力することは可能です。
* APIの設計や設定  
  graphql-code-generatorの設計はgraphqlgenの開発者が満足できるものではなかったようです。

# 使ってみた感想
* graphqlgenはリゾルバに特化しているのでシンプルな一方で、スキーマ以外にも用意しないといけないファイルがあるのでちょっと面倒です。それに対して、graphql-code-generatorはスキーマを食わせるだけで、リゾルバーのインターフェスはもちろんモデルも生成してくるのでかなり便利だと思います。
* graphql-code-generatorはファイルごとに変換処理が独立しているので、共通するコードを切り分けてimportしてくれません。
  そうすると、`typescript-common`の出力結果である列挙型の定義がどちらのファイルにも現れてしまいます。
  ファイルごとに使うプラグインを定義するので難しいかもしれませんが、もう少し上手い方法があってもいいと思います。
  それに対してgraphqlgenはファイルの分割戦略を設定で変更することができ、importもしてファイルを関連付けてくれます。

# まとめ
今回はGraphQLのスキーマからTypescriptなどのコードを生成するツールであるgraphql-code-generatorとgraphqlgenを紹介しました。
graphql-code-generatorのほうが機能は充実しているのですが、graphqlgenのいいところ(ファイルの分割戦略)もあります。
GitHubのissueを見ると対立的な雰囲気があるので、お互いが協力しながら一つのよいOSSを作り上げていってほしいと思っています。