【Go言語】はじめてのGraphQLサーバ実装 | gqlgen

# この記事について

この記事は、Go言語でGraphQLサーバの開発をする方法を書きます。

### この記事で書くこと

- GraphQLのスキーマ定義
- Go言語でGraphQLサーバの開発方法
    - フレームワークとして[gqlgen](https://gqlgen.com)を利用
- エラー処理
- 負荷対策
    - N+1問題
    - クエリの大きさ判定

### この記事で書かないこと

- Go言語の説明
- GraphQLの概要について
- バックエンドのデータベース(MySQL等)への接続方法

### ソースコード

この記事で紹介するコードはGithubに置いております。
https://github.com/hiroyky/go_graphql_server_sample

# 前提条件

- Go言語の基本的な知識があり、お使いのマシンでGo言語のビルドや実行ができる。
- GraphQLの概要について知識がある。

GraphQLの概要は[公式](https://graphql.org)が詳しく説明をしています。
また、2021年11月23日現在 Go-1.17で試験しております。

# 開発の開始
GraphQLのフレームワークgqlgenを使ってGraphQLサーバの構築を始めます。

## 初期化
新規リポジトリに描きコマンドを順に実行してGo言語プロジェクトの初期化、GraphQLのフレームワークgqlgenの導入を行います。

```shell
$ go mod init
$ go get github.com/99designs/gqlgen
$ go run github.com/99designs/gqlgen init
```

これによって下記のファイル群が作成されています。

```
├── gqlgen.yml: コード自動生成の設定ファイル
├── graph
│   ├── generated
│   │   └── generated.go // 編集しないファイル
│   ├── model
│   │   └── models_gen.go // GraphQLで使う型が定義されているファイル
│   ├── resolver.go // リゾルバの定義
│   ├── schema.graphqls // GraphQLのスキーマ定義ファイル
│   └── schema.resolvers.go // 実際の処理を書くファイル
└── server.go // HTTPサーバの起動プログラム
```

この段階でも下記を実行すれば、http://localhost:8080/ でプレイグラウンド画面を開くことができます。

```
$ go mod tidy
$ go run ./server.go
```

下図のプレイグラウンド画面でGraphQLを試すことができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/28de2004-6cad-43b9-ea5a-1bc53c6b0e3a.png)

ここまでの内容をこちらに設置しました。
https://github.com/hiroyky/go_graphql_server_sample/pull/1

## GraphQLサーバ開発の流れ
先ほどの自動生成で、下記の3ファイルも実装され内容は以下のようになっています。

- `schema.graphqls`にTODOリストを模したスキーマが定義されています。
- `model/model_gen.go`にGo言語でその型定義が記述されています。
- `schema.resolvers.go`に処理を実装するための雛形が記載されています。

プログラムの作成手順は以下の流れになります。

1. `*.graphqls`にGraphQLのスキーマ定義を書く。
1. gqlgenの自動生成コマンド(`gqlgen`)を実行する。
1. 作成・更新された`model/model_go.go`ファイルを確認し、必要に応じて別ファイルに移して編集する。
1. `*.resolvers.go`ファイルに処理本体を実装する。

`*.graphqls`ファイルと`*.resolvers.go`ファイルは対になっています。

## GraphQLのスキーマ定義(の復習)

GraphQLのスキーマ定義は、データの型定義と関数(メソッド)の定義で行います。どちらもGo言語やその他の言語の構造体・クラスの定義と同じ要領です。

型定義では、構造体名を決めて中身のフィールドを定義してきます。フィールドの型の基本形として`String`, `Int`, `Boolean`が用意されています。他に、プライマリキーであることを示す`ID`、列挙対、インターフェイス、独自型定義の仕組みが用意されています。
インターフェイスは共通するフィールドを定義します。インターフェイスを実装した型はかならずそのフィールドを持たなければイケません。プログラム言語にあるインターフェイスとほぼ等価と言えるでしょう。

関数(メソッド)の定義は2つに分かれています。取得系を定義する`query`と更新系を定義する`mutation`です。どちらも関数名、引数、戻り値を記述します。

[公式の説明「Schemas and Types」](https://graphql.org/learn/schema/)

## スキーマ定義とコード生成
### ”会社と部署、会社員”という題材で設計

自動生成されたTODOリストのスキーマを題材に進めても良いのですが、ここでは会社(company)と部署(department),会社員(employee)というよくある題材で考えてみたいと思います。題材をMySQLのデータベーステーブルのER図で表すと下記のような構成です。本記事ではバックエンドのデータベースには触れませんがMySQLを使っている想定で話を進めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/4c6151ca-c9f5-4a3a-4b5b-0665f694271a.png)

`graph/model/schema.graphqls`と`graph/model/scheme.resolvers.go`を削除して新規にスキーマ定義ファイルを作成しましょう。

```shell
$ rm ./graph/model/schema.graphqls
$ rm ./graph/model/schema.resovers.go
```

### スキーマを定義する1[基本]

最初なので型定義の基本的な部分から順番に行いましょう。いずれも`graph/`に設置します。

最初に全体で共用するインターフェイス`Node`を定義します。プライマリキー`id`を持つ型にはNodeインターフェイスを実装するのがお作法のようです。また独自型として`Timestamp`を定義しています。型末尾の`!`は必須である(NULL値を許容しない)ことを示しています。

```graphql:common.graphqls
interface Node {
    id: ID!
}

scalar Timestamp
```

それでは早速、インターフェイス`Node`の実装として`Company(会社)`, `Department(部署)`,`Employee(社員)`の型を定義していきましょう。Employeeでは列挙対`Gender`も定義しています。(昨今では性別にMale, Female以外にも対応すべきと言う議論もありますが、ここでは無しで)

```graphql:company.graphqls
type Company implements Node {
    id: ID!
    companyName: String!
    representative: String!
    phoneNumber: String!
}
```

```graphql:department.graphqls
type Department implements Node {
    id: ID!
    departmentName: String!
    email: String!
}
```
```graphql:employee.graphqls
type Employee implements Node {
    id: ID!
    name: String!
    gender: Gender!
    email: String!
    latestLoginAt: Timestamp!
    """ 扶養家族の人数 """
    dependentsNum: Int!
    """ 管理職かどうか """
    isManager: Boolean!
}

enum Gender {
    Male
    Female
}
```

Go言語や他の言語の構造体・クラスでのメンバ変数の定義とほぼ同じですね。

### スキーマを定義する2 [他の型と紐付け]

それでは、更に型同士の紐付けも定義に組み込んでいきましょう。GraphQLの型定義ではリレーショナルデータベースでのリレーション(関係性)を持たせることができます。

まずは`Employee`から編集します。`department`と`company`フィールドを追記します。こうすることでEmployeeから所属しているDepartmentやCompanyをたどれるようになります。そういえばデータベースのER図には`department_id`や`company_id`がありましたね。

```graphql:employee.graphqls
type Employee implements Node {
    id: ID!
    name: String!
    gender: Gender!
    email: String!
    latestLoginAt: Timestamp!
    """ 扶養家族の人数 """
    dependentsNum: Int!
    """ 管理職かどうか """
    isManager: Boolean!
    department: Department! # 追記
    company: Company!       # 追記
}

enum Gender {
    Male
    Female
}
```

### スキーマを定義する3 [ページネーション]

続いて`Department`や`Company`にも同様に関連テーブルへの紐付けを追加していこうと思います。CompanyからDepartment、Employeeは1対多の関係です。従って配列で値を返すことになります。しかしながらAPIとして全件を毎回一括で返すわけにもいかないので、ページネーションを定義しましょう。（全件を一括返すような設計では、データベースが負荷で落ちますからね。）

また今回はLimit,Offsetによる一般的なページネーションを採用します。多くのリレーショナルデータベースではlimit,offsetで取得件数・取得位置を指定しますが、それと同じです。GraphQLでは[カーソル式のページネーション](https://www.apollographql.com/docs/react/pagination/cursor-based/)を採用する場合も多いようですが、今回は見送ります。

::: note info
個人的な意見として、カーソル式のページネーションは、ツイッターやフェイスブックのタイムラインのように時々刻々とレコードが挿入されるデータには適していると思いますが、そうでないデータであれば、無理矢理カーソル式のページネーションを採用するのではなくLimit,Offsetによるページネーションの採用も検討すべきと思います。今回は会社と社員一覧と時々刻々と変化するデータでは無いのでLimit,Offsetによるページネーションが適していると考えます。
:::

ページネーション定義の準備として`common.graphqls`に下記を追記します。

```graphql
interface Pagination {
    pageInfo: PaginationInfo!
    nodes: [Node!]! # Node型の配列という意味
}

type PaginationInfo {
    page: Int!
    paginationLength: Int!
    hasNextPage: Boolean!
    hasPreviousPage: Boolean!
    count: Int!
    totalCount: Int!
}
```

それではDepartmentに紐付けを追加します。`EmployeePagination`でページ位置情報と`Employee`の配列をフィールド名`nodes`で定義しています。

```graphql:employees.graphqls
# 下記を追記
type EmployeePagination implements Pagination {
    pageInfo: PaginationInfo!
    nodes: [Employee!]!
}
```

```graphql:department.graphqls
type Department implements Node {
    id: ID!
    departmentName: String!
    email: String!
    company: Company!              # 追記
    employees: EmployeePagination! # 追記
}
```

同様にCompanyにも紐付けを追加します。

```graphql:department.graphqls
# 下記を追記
type DepartmentPagination implements Pagination {
    pageInfo: PaginationInfo!
    nodes: [Department!]!
}
```
```graphql:company.graphls
type Company implements Node {
    id: ID!
    companyName: String!
    representative: String!
    phoneNumber: String!
    departments: DepartmentPagination! # 追記
    employees: EmployeePagination!     # 追記
}
# 後ほど使うので併せて定義
type CompanyPagination implements Pagination{
    pageInfo: PaginationInfo!
    nodes: [Company!]!
}
```

### スキーマを定義する4 [queryとmutation]

ここまでは型定義でしたが、関数(メソッド)の定義を行います。取得系の`query`と更新系の`mutation`を定義します。これらをラップする型名はそれぞれ`Query`, `Mutation`である必要があります。
関数の定義方法は一般的なプログラミング言語のそれと似ているので直感的に分かると思います。`!`が引数に付いていれば必須項目となり、戻り値についていればレスポンスがNULLで無いことが保証されているという意味になります。

```
関数名(引数1: 引数の型1!, 引数2: 引数の型2..): 戻り値の型
```

queryでは各型に対して単数形と複数形で取得関数を定義しています。いずれもlimit,offsetで取得件数と取得位置を指定できるようにしています。なおlimitは必須になっています。`employees`ではそれ以外にも絞り込み項目を定義しました。

```graphql:query.graphqls
type Query {
    company(id: ID!): Company
    companies(limit: Int!, offset: Int): CompanyPagination!
    department(id: ID!): Department
    departments(limit: Int!, offset: Int): DepartmentPagination!
    employee(id: ID!): Employee
    employees(
        limit: Int!,
        offset: Int,
        email: String
        gender: Gender,
        isManager: Boolean,
        hasDependent: Boolean
    ): EmployeePagination!
}
```

更新系のミュテーションを`mutation.graphqls`に定義します。ミュテーションの引数は`input`としてまとめています。引数の型定義は`type`ではなく`input Xxxx {}`で記述します。戻り値は、作成・更新した場合には新しい値を、削除の場合はいったん`true`を固定で返す定義です。

```graphql:mutation.graphqls
type Mutation {
    createCompany(input: CreateCompanyInput!): Company!
    updateCompany(input: UpdateCompanyInput!): Company!
    deleteCompany(id: ID!): Boolean!
    createDepartment(input: CreateDepartmentInput!): Department!
    updateDepartment(input: UpdateDepartmentInput!): Department!
    deleteDepartment(id: ID!): Boolean!
    createEmployee(input: CreateEmployeeInput!): Employee!
    updateEmployee(input: UpdateEmployeeInput!): Employee!
    deleteEmployee(id: ID!): Boolean!
}

input CreateCompanyInput {
    companyName: String!
    representative: String!
    phoneNumber: String!
}

input UpdateCompanyInput {
    id: ID!
    companyName: String
    representative: String
    phoneNumber: String
}

input CreateDepartmentInput {
    departmentName: String!
    email: String!
}

input UpdateDepartmentInput {
    id: ID!
    departmentName: String
    email: String
}

input CreateEmployeeInput {
    name: String!
    gender: Gender!
    email: String!
    dependentsNum: Int!
    isManager: Boolean!
}

input UpdateEmployeeInput {
    id: ID!
    name: String
    gender: Gender
    email: String
    dependentsNum: Int
    isManager: Boolean
}
```

ここまでの更新をGithubのプルリクにまとめました。ブランチは`feat2`です。
https://github.com/hiroyky/go_graphql_server_sample/pull/2/files


## コード生成
### コード生成1 [gqlgenを実行]

スキーマ定義ができたら`gqlgen`コマンドでスキーマ定義を元にGo言語のプログラムを自動生成します。

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

下記のような構成でGo言語のファイルが生成されていると思います。

```
./graph
├── generated
│   └── generated.go
├── model
│   └── models_gen.go
├── mutations.resolvers.go
├── query.resolvers.go
└── resolver.go
```

`query.resolvers.go`と`mutations.resolvers.go`には、先ほど定義した関数がGo言語の関数で定義されています。
引数の定義で`!`を付けて必須化した引数は値ですが、必須で無い引数はポインタ型になっています。未指定の場合はNULLが入ります。

```go:query.resolvers.go
// ・・・・
func (r *queryResolver) Employee(ctx context.Context, id string) (*model.Employee, error) {
	panic(fmt.Errorf("not implemented"))
}

func (r *queryResolver) Employees(ctx context.Context, limit int, offset *int, email *string, gender *model.Gender, isManager *bool, hasDependent *bool) (*model.EmployeePagination, error) {
	panic(fmt.Errorf("not implemented"))
}
// ・・・・
```

### コード生成2 [構造体を編集して再生成]

ところで、`model/models_gen.go`で定義した型を確認しましょう。Employeeは次のように定義されています。スキーマ定義に書いたコメントも反映されていますね。

```go:model/models_gen.go
type Employee struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Gender        Gender `json:"gender"`
	Email         string `json:"email"`
	LatestLoginAt string `json:"latestLoginAt"`
	//  扶養家族の人数
	DependentsNum int `json:"dependentsNum"`
	//  管理職かどうか
	IsManager  bool        `json:"isManager"`
	Department *Department `json:"department"`
	Company    *Company    `json:"company"`
}
```

さて、ここで着目しなければいけないのは`Department`と`Company`です。一見問題ないように見えますが、問題があるのです。
バックエンドのデータベースにMySQL等のリレーショナルデータベースを使ってる場合、データベースのテーブルとしては`Department`と`Company`が直接入るのではなく`department_id`,`company_id`といった外部キーのIDになると思います。そしてCompanyやDepartmentの中身が欲しいときは、別途SQLを実行するか結合を含んだSQLを実行する必要があります。GraphQLではリクエストのクエリにDepartmentやCompanyの指定があったときだけ別途SQLを実行するような柔軟な仕様にできます。つまづきポイントの1つだと思うのでまずは一緒にやってみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73358/28055ec5-5242-45e8-0d46-c53f1114ee64.png)

自動生成のモデル型を変更します。ただ`models/models_gen.go`は自動生成の度に再生成されるのでこのファイルを直接編集はしません。代わりに同じディレクトリ(パッケージ)に`models/models.go`を新規作成して下記のように記述します。その際に`models_gen.go`の`Employee`構造体を削除します。
着目して欲しいのは `Department *Department`や`Company *Company`を削除して代わりに`DepartmentID string`と`CompanyID    string`を定義しています。これでデータベースのテーブル定義に近づきました。IDはint型では？という声が聞こえてきそうですがstring型にします。理由は後述します。

```go:model/models.go
package model

type Employee struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Gender        Gender `json:"gender"`
	Email         string `json:"email"`
	LatestLoginAt string `json:"latestLoginAt"`
	//  扶養家族の人数
	DependentsNum int `json:"dependentsNum"`
	//  管理職かどうか
	IsManager    bool   `json:"isManager"`
	DepartmentID string `json:"department"` // Departmentを削除して、代わりにDepartmentIDを記述
	CompanyID    string `json:"company"`    // Companyを削除して、代わりにCompanyIDを記述
}
func (Employee) IsNode() {}
```

ここまで編集したら改めて`gqlgen`コマンドを実行します。

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

するとどうなるでしょうか？新たに`employee.resolvers.go`が生成されました。これには2つの関数が定義されています。第二引数で`Employee`を受け取り、`Department`や`Company`を返す関数です。
これは、先ほど自身で定義し直した`Employee`について`gqlgen`が`Department`と`Company`が不足していたことを検知して、不足分を取得するための関数を定義したのです。関数の第2引数では親であるEmployeeが渡されるのでそれを手がかりに子の`Company`や`Department`を取得する処理を実装します。

```go:employees.resolvers.go
func (r *employeeResolver) Department(ctx context.Context, obj *model.Employee) (*model.Department, error) {
	panic(fmt.Errorf("not implemented"))
}

func (r *employeeResolver) Company(ctx context.Context, obj *model.Employee) (*model.Company, error) {
	panic(fmt.Errorf("not implemented"))
}
```

これらの関数はクライアントが`Employee`のクエリで`department`や`company`を指定したときのみ実行されます。必要でなければ呼び出されません。たとえば以下のリクエストクエリではdepartment()は呼び出されますがcompany()は指定していないので呼び出されません。

```graphql
query {
   employee(id:"RW1wbG95ZWU6MQ==") {
      id
      name
      department {
         id
         departmentName
      }
   }
}
```

さて、それでは他のCompanyやDepertmentの構造体も同様に編集していきます。`models_gen.go`から`models.go`に移動して追加削除をします。

```go:model/models.go
type Company struct {
	ID             string `json:"id"`
	CompanyName    string `json:"companyName"`
	Representative string `json:"representative"`
	PhoneNumber    string `json:"phoneNumber"`
	// Departmentsのフィールド自体を削除
	// Employeesのフィールド自体を削除
}
func (Company) IsNode() {}

type Department struct {
	ID             string `json:"id"`
	DepartmentName string `json:"departmentName"`
	Email          string `json:"email"`
	CompanyID      string `json:"company"` // Companiesを削除して、CompanyIDを追記
	// Employeesのフィールド自体を削除
}
func (Department) IsNode() {}
```

ここまで編集したら改めて`gqlgen`コマンドを実行します。

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

先ほどと同様に構造体とGraphQLのスキーマ定義で乖離している部分を`gqlgen`が検知して不足部分を補うための関数が生成されました。例えば新規作成された`companies.resolvers.go`は以下のように親である`Company`を第二引数に受けて`DepartmentPagination`, `EmployeePagination`を返す関数が定義されています。

```go:companies.resolvers.go

func (r *companyResolver) Departments(ctx context.Context, obj *model.Company) (*model.DepartmentPagination, error) {
	panic(fmt.Errorf("not implemented"))
}

func (r *companyResolver) Employees(ctx context.Context, obj *model.Company) (*model.EmployeePagination, error) {
	panic(fmt.Errorf("not implemented"))
}
```

ここまでの編集をプルリクにまとめました。ブランチはfeat3です。
https://github.com/hiroyky/go_graphql_server_sample/pull/3

## 中身の実装とプライマリキーの注意点

さて、ここからは自動生成されたリゾルバ関数の中身の実装を行います。バックエンドのデータベースに接続したり、別のREST APIにリクエストしたりなどです。本記事ではデータベースやREST APIとの通信は趣旨からずれるので記述しません。ぜひご自身で中身の実装をすすめてください。

ただ、1点注意事項があります。それはプライマリキー`ID`を型を横断してグローバルでユニークにするということです。つまり、`Employee`のidが`1,2,3...`と連番で`Department`のidも`1,2,3..`と連番ではIDはグローバルでユニークではありません。そこでプライマリキーを次のように型名とセットで記述します。

- Employee:1
- Employee:2
- Employee:3
- ...
- Department:1
- Department:2
- Department:3
- ...

GraphQLでは更にこれをBase64エンコードした物をプライマリキーとして扱うのが一般的です。

- base64(Employee:1) = "RW1wbG95ZWU6MQ=="
- base64(Department:1) = "RGVwYXJ0bWVudDox

これを実現するために構造体のidをint型ではなくstring型にしました。バックエンドデータベースのMySQL等ではintの連番でプライマリキーを管理していても、GraphQLのレスポンスやリクエスト引数では型名とセットにした文字列(`型名:番号`)のbase64文字列に変換すべきです。理由は、GraphQLでは、型関係なくID自体でキャッシュ等を管理するので、IDの重複を避けるためです。従って、プライマリキーにUUIDなどのそれ自体が唯一無二な形式を用いているのであれば変換をする必要はありません。
[Global Object Identification](https://graphql.org/learn/global-object-identification/)に解説があります。

# エラー処理

リゾルバでエラーを生成して、エラーレスポンスを返す必要な場面があります。gqlgenフレームワークはその機能も備えています。

GraphQLのレスポンスJSONは正常系であればで`data`のみを返しますが、エラーレスポンスでは`errors`配列を返します。エラーには次の項目を含めます。

- message: エラーの内容を簡潔に伝えるメッセージ
- path: エラーが起こった場所(クエリの位置)
- extensions: それ以外にクライアントに伝えたい内容があれば`key:value`形式で記述

```json
{
    "data": {},
    "errors": [
        { 
            "message": "Error: hoge fuga", 
            "path": [ "employee" ], 
            "extensions": {"key1": "value1"} 
        },
        { "message": "Error: foo bar", "path": [ "department" ] },
    ]
}
```

これを実現するために `server.go`のgraphqlサーバを編集します。サーバインスタンスの`SetErrorPresenter`の引数にエラーレスポンスを生成する関数を書きます。引数のerrorにリゾルバ等で投げられたエラーが入るのでそれを元にエラーレスポンスを生成します。`gqlerror.Error`型をこの関数で返します。

```go:server.go
    srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
	// エラー処理を書く
	srv.SetErrorPresenter(func(ctx context.Context, e error) *gqlerror.Error {
		err := graphql.DefaultErrorPresenter(ctx, e)
		err.Message = e.Error()
		err.Extensions = map[string]interface{}{
			"key1": "value1",
			"key2": "value2",
		}
		return err
	})
```

この内容をプルリクにしています。ブランチはfeat4
https://github.com/hiroyky/go_graphql_server_sample/pull/4

公式の文献は https://gqlgen.com/reference/errors/

# 負荷対策
## N+1問題対策
### 概要
GraphQLあるあるの問題としてN+1問題があります。クエリで子要素を参照したときに、都度リゾルバが実行されるため、バックエンドのデータベースやAPIに大量のリクエストが発生する現象です。
例えば、下記のようにEmployeeの配列から所属Companyを参照するクエリを書いた場合です。この時、company()リゾルバが都度実行されます(最大100回)。従ってバックエンドのデータベース等へもリクエストが都度発生するためデータベースへの負荷が大きくなります。

```graphql
query {
  employees(limit:100) {
    nodes {
      name
      company {
        companyName
      }
    }
  }
}
```

```sql
SELECT * FROM companies WHERE company_id=1;
SELECT * FROM companies WHERE company_id=1;
SELECT * FROM companies WHERE company_id=2;
SELECT * FROM companies WHERE company_id=3;
-- ・・・
```

この「N+1問題」の解決策として`dataloader`を用いた手段があります。これは毎回バックエンドへリクエストを発生するのではなく、ある一定期間処理を溜めてからバックエンドにリクエストをまとめて送るようにするものです。これにより下記のように1クエリにまとめられます。

```sql
SELECT * FROM companies WHERE company_id IN (1,2,3);
```

### 導入・実装
実現するために [dataloaden](https://github.com/vektah/dataloaden)を利用します。

```shell
$ go get github.com/vektah/dataloaden
```

他の言語のdataloaderではジェネリクス型等で対応するようですが、Go言語のdataloaderはコード生成します。

```shell
$ mkdir dataloader
$ cd dataloader
$ echo "package dataloader" > gen.go
$ go run github.com/vektah/dataloaden CompanyLoader string "*github.com/hiroyky/go_graphql_server_sample/graph/model.Company"
```

`dataloader/companyloader_gen.go`が生成されました。`Company`の取得では、このCompanyLoaderを使うように編集してみましょう。

まずは各リゾルバから参照できるように`resolver.go`にフィールドを設けます。

```go:graph/resolver.go
type Resolver struct{
	CompanyLoader *dataloader.CompanyLoader // 追記
}
```

`server.go`でGraphQLサーバ起動時にCompanyLoaderを生成して、渡すようにします。（main関数に書くべき内容ではないと思いますが、簡略化のためにmain関数に書きます。）

Fetchにデータをまとめて取得する処理を書きます。引数の`keys`には、呼び出し時に溜めた引数がまとめて渡されます。`keys`の順番通りに値を返します。

```go:server.go
func main() {
// ・・・省略・・・
	companyLoader := dataloader.NewCompanyLoader(dataloader.CompanyLoaderConfig{
		MaxBatch: 100, // 溜める最大数、0を指定すると制限無し
		Wait:     2 * time.Millisecond, // 溜める時間
		Fetch: func(keys []string) ([]*model.Company, []error) {
			companies := make([]*model.Company, len(keys))
			errors := make([]error, len(keys))

			// 取得処理を書く SELECT * FROM company WHERE company_id IN (...)

			// 引数のkeysに対応する順番の配列で返す。
			return companies, errors
		},
	})

	srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{
		CompanyLoader: companyLoader,
	}}))
// ・・・省略・・・
}
```

呼び出しは以下のように行います。`Company.Load`を呼び出すことで、先ほど`Fetch`に定義した関数が実行されます。即時都度実行されるわけではなく、溜めてからまとめて実行されます。

```go:employee.resolvers.go
func (r *employeeResolver) Company(ctx context.Context, obj *model.Employee) (*model.Company, error) {
	return r.CompanyLoader.Load(obj.CompanyID)
}
```

dataloaderの生成コマンドについて改めてまとめます。

```shell
$ cd dataloader
$ echo "package dataloader" > gen.go

■int型を引数に取る関数でロードしたい場合
先ほどはコマンドの第二引数に`string`を指定しました。これは`Fetch関数`の引数の型に対応します。従って`CompanyLoader.Load()`関数の引数を`int`型にしたい場合は`int`を指定します。
$ go run github.com/vektah/dataloaden CompanyLoader int "*github.com/hiroyky/go_graphql_server_sample/graph/model.Company"

■配列を戻り値のロード関数を生成したい場合
`Load()`関数で配列を戻り値にすることもできます。`Fetch`関数の配列処理の実装が少し複雑になるので注意してください。おそらく二重のforループが発生するため。
$ go run github.com/vektah/dataloaden CompaniesLoader int "[]*github.com/hiroyky/go_graphql_server_sample/graph/model.Company"
```

以上、N+1問題を解決するためにdatalodaerを導入しました。
ここまでをプルリクにまとめました。ブランチはfeat5
https://github.com/hiroyky/go_graphql_server_sample/pull/5

公式文献：
 - https://gqlgen.com/reference/dataloaders/
 - https://github.com/vektah/dataloaden

## クエリの重さ制限
### 概要
GraphQLのAPIでは、クライアント側が自由にクエリを作成してリクエストすることができます。一方で、負荷が重いクエリも簡単にリクエストできてしまう問題があります。そこでクエリを実行する前に、クエリの重さを計算して一定以上であれば実行せずにエラーレスポンスを返すという機能があります。
GraphQLのAPI自体をグローバルに公開する場合には導入が必須かとは思いますが、アクセス元が社内サーバに限定されるなどの場合には、アクセス元のシステムを開発しているチームとクエリ内容を事前に相談するという解決策でも良いかと思います。

### 導入・実装

導入の仕方自体は簡単で、端的に言えば下記一行を`server.go`に足すだけです。引数に重さの上限を指定します。1項目辺り1として計算され、この値を超えたらエラーレスポンスを返します。

```go
srv.Use(extension.FixedComplexityLimit(10)) // 重さが10を超えたらエラーにする
```

しかしながら、全ての項目が等しい負荷であるわけがありません。例えば配列を返す項目や、更新系のミュテーションは通常よりも重いクエリのはずです。そこで、スキーマの項目毎に計算関数を定義できます。

これまではサーバのNew関数に直接渡していた引数である設定を編集します。

```go:server.go
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{CompanyLoader: companyLoader}}))
```

上記を下記のようにします。`c.Complexity`以下で重さを計算する関数を定義します。関数の第一引数にはリクエストで要求されたレスポンスの項目数、第二引数にはリクエストの引数が与えられます。それらを元に重さを計算して数値で返します。

```go:server.go
	c := generated.Config{Resolvers: &graph.Resolver{CompanyLoader: companyLoader}}
	c.Complexity.Mutation.CreateCompany = func(childComplexity int, input model.CreateCompanyInput) int {
		return 5
	}
	srv := handler.NewDefaultServer(generated.NewExecutableSchema(c))
```

以上、クエリの重さ計算の処理でした。
といいつつも、アクセス元が社内サーバだけに限定してアクセス元の開発チームとクエリ内容を事前相談しておくということが可能であれば、そうしたほうが良い気もしますが..

# まとめ

`gqlgen`を使ったGO言語でのGraphQLサーバの実装について記述しました。
`gqlgen`での開発手順は下記の手順でした。これの繰り返しです。

1. スキーマ定義
1. コード生成
1. 生成されたコードの調整
1. コード生成
1. リゾルバに処理を書く


他に、エラー処理や負荷対策を記述しました。
拝読ありがとうございました。お疲れ様でした。