gqlgenのチュートリアルを試しました

## はじめに
初めまして、[ｋ．ｓ．ロジャース](https://www.wantedly.com/companies/ks-rogers)の西谷です。

直近では、iOS, Android用のREST APIを開発しております。
このときに、画面描画用データ取得APIが350kb程のレスポンスを返すことがあり、結果としてアプリの描画速度に影響を与えました。
原因はレスポンスのネストしたEntityサイズで、運用当初はデータ量が少ないため問題はありませんでしたが、
運用しているうちにデータが増加することでEntityサイズが膨れ上がりました。

このときの対応としては、レスポンスから不要な内容を削除し、350kbから20kbまで減らすことで正常化できました。
しかし、今回は偶然不要データでしたが本来は返すべき内容ですので、方針として特例を多く作りたくない気持ちがありました。

|  | レスポンスサイズ | レスポンス速度 | アプリの画面表示までの時間 |
|--------|----------------------|-------------------|-------------------------------|
| 全データを返す | 350kb | 9.5秒 | 15~20秒 |
| 必要データだけ返す | 20kb | 2.5秒 | 5秒 |

そこで、各画面・機能にとって必要なデータだけを柔軟に取得できるGraphQLについて調査しました。
本来GraphQLで処理速度が速くなるわけではありませんが、上記の場合は不要Entityを取得しないため、サーバ側の処理速度改善が期待できると考えています。

間違い・助言等があればコメントにてお知らせいただけたらと思います。

## なぜgqlgenなのか
まず、Golangで開発したい理由があります。
弊社ではGolangのボイラーテンプレート(REST, gRPC)を開発しており、GraphQLを採用するとなった場合はGolangで実装する可能性が高いです。

golangで実装可能なフレームワークは[こちら](https://tech.hicustomer.jp/posts/go-graphql/)を参考にさせて頂きました。
弊社ではiOSやAndroidチームと連携して開発を進めることが多いため、スキーマ駆動かつドキュメントが豊富なgqlgenを選択しました。


## 環境構築
[公式のチュートリアル](https://gqlgen.com/getting-started/)に従って進めます。

まずプロジェクトの作成を行います。

```shell
mkdir gqlgen-todos
cd gqlgen-todos
go mod init github.com/[username]/gqlgen-todos
```

スキーマファイルを作成します。

```schema.graphql
# Todoのデータ保持
type Todo {
  id: ID!
  text: String!
  done: Boolean!
  user: User!
}

# Userのデータ保持
type User {
  id: ID!
  name: String!
}

# リクエストの定義(Todo作成)
input NewTodo {
  text: String!
  userId: String!
}

# データ取得APIの定義
type Query {
  todos: [Todo!]!
}

# データ変更APIの定義
type Mutation {
  createTodo(input: NewTodo!): Todo!
}
```

データ取得関連のAPIはQueryに、新規作成・更新・削除はMutationに定義することになります。

## プロジェクトの雛形作成
スキーマファイル作成後に`go run github.com/99designs/gqlgen init`で雛形を生成できます。

```
.
├── generated.go # gqlgenの生成ファイル(基本的に触らない)
├── go.mod 
├── go.sum
├── gqlgen.yml # 設定ファイル
├── models_gen.go # スキーマから生成されたモデル
├── resolver.go # 処理の実装をここに書く
├── schema.graphql
└── server
    └── server.go
```

## サーバ側の実装
### Todoモデルの定義
Todoのモデルを新規定義します。
`models_gen.go`で自動生成されたモデルはUserも含まれています。
しかし、リクエストでユーザデータを指定されたときのみ、データ取得するようにしたいためtodoを再定義しています。

```todo.go
package gqlgen_todos

type Todo struct {
	ID     string
	Text   string
	Done   bool
	UserID string
}
```

gqlgen.ymlに新規作成したtodoの内容を追加します。

```gqlgen.yml
schema:
- schema.graphql
exec:
  filename: generated.go
model:
  filename: models_gen.go
resolver:
  filename: resolver.go
  type: Resolver
# 追加
models:
  Todo:
    model: github.com/[username]/gqlgen-todos.Todo
```

再ビルドします。

```shell
go run github.com/99designs/gqlgen
```


### resolversの実装
現在の仕様でgqlgenを実行しても、すでに存在する`resolver.go`を変更出来ないようです。(対応予定)
ですので、一度`resolver.go`削除して再生成します。

```shell
rm resolver.go
go run github.com/99designs/gqlgen
```

これで`resolver.go`が最新になり、`not implemented`のメソッドを実装して完了です！

```resolver.go
package gqlgen_todos

import (
	context "context"
	"fmt"
	"math/rand"
)
// 追加
type Resolver struct {
	todos []*Todo
}

func (r *Resolver) Mutation() MutationResolver {
	return &mutationResolver{r}
}
func (r *Resolver) Query() QueryResolver {
	return &queryResolver{r}
}
func (r *Resolver) Todo() TodoResolver {
	return &todoResolver{r}
}

type mutationResolver struct{ *Resolver }

func (r *mutationResolver) CreateTodo(ctx context.Context, input NewTodo) (*Todo, error) {
	// 追加
	todo := &Todo{
		Text:   input.Text,
		ID:     fmt.Sprintf("T%d", rand.Int()),
		UserID: input.UserID,
	}
	r.todos = append(r.todos, todo)
	return todo, nil
}

type queryResolver struct{ *Resolver }

func (r *queryResolver) Todos(ctx context.Context) ([]Todo, error) {
	// 追加
	return r.todos, nil
}

type todoResolver struct{ *Resolver }

func (r *todoResolver) User(ctx context.Context, obj *Todo) (*User, error) {
	// 追加
	return &User{ID: obj.UserID, Name: "user " + obj.UserID}, nil
}
```

## 実行
` go run server/server.go`でモックサーバが起動して`http://localhost:8080/`からテストできます。

後は次のようにTodo追加と一覧取得ができます。



<img width="1054" alt="スクリーンショット 2019-06-27 4.40.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274674/db65b206-7429-a738-f0d4-64d5c6ca5342.png">

<img width="1077" alt="スクリーンショット 2019-06-27 4.40.53.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274674/fb47be21-9166-55b2-b03d-91f8ef8431f7.png">

## おわりに
今回はgqlgen公式のチュートリアルを試してみました。
現在はシンプルですが、実用にあたっては対応すべき課題が多々あると思います。
今後も時間を見つけて調査を進めたいと思います。


### Wantedlyでもブログ投稿してます
Techブログに加えて会社ブログなどもやっているので、気になった方はぜひ覗いてみてください。
https://www.wantedly.com/companies/ks-rogers