golangでGraphQLの素振りを行った

golangでGraphQLの素振りを行ったので，詰まったところ実装周りを書いていきます．


## GraphQLとは

一つのエンドポイントに独自のクエリ言語を投げることで，多くのリソースを一回のリクエストで大量にゲットできるすごいもの．
RESTだと一つのエンドポイントごとにリソースを取得するのが，普通だと思うのでどうしてもエンドポイントが増えていきます．他にもリソースが増えてくると，複数リクエストを送らないといけなかったりいろいろきついです．
そんな問題を解決するために，GraphQLは出てきました．

こちらの記事👇にすごく詳しく書かれているので，ぜひ読んでください．

[GraphQL入門 - 使いたくなるGraphQL](https://qiita.com/bananaumai/items/3eb77a67102f53e8a1ad)

## golangでのGraphQL実装

ライブラリがあるので，それを使用します．

今回使用したライブラリ👇

[graphql-go/graphql](https://github.com/graphql-go/graphql)

## 書いたもの

実際に書いたコードは👇に置いています．

[mitubaEX/graphQL_sample](https://github.com/mitubaEX/graphQL_sample)

## 少し解説

まず **main.go** を見てみましょう．

```golang:main.go
package main

import (
	"encoding/json"
	"fmt"
	"github.com/graphql-go/graphql"
	"github.com/mitubaEX/graphQL_sample/application/graphql_util"
	"io/ioutil"
	"net/http"
)

func executeQuery(query string, schema graphql.Schema) *graphql.Result {
	// schema と もらってきた query を入れて， graphql を実行
	result := graphql.Do(graphql.Params{
		Schema:        schema,
		RequestString: query,
	})
	if len(result.Errors) > 0 {
		fmt.Printf("wrong result, unexpected errors: %v", result.Errors)
	}
	return result
}

func main() {
	// POST /graphql のリクエストを受け取れるようにする
	http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
		defer r.Body.Close()

		// bodyの読み取り処理
		body, err := ioutil.ReadAll(r.Body)
		if err != nil {
			panic(err)
		}

		// query 実行
		result := executeQuery(fmt.Sprintf("%s", body), graphql_util.Schema)
		json.NewEncoder(w).Encode(result)
	})

	fmt.Println("Now server is running on port 8080")
	http.ListenAndServe(":8080", nil)
}
```

GraphQLを実行するためには，`schema`と`query`が必要です．`query`は，`request`でもらったものをそのまま使っていて，`schema`は`graphql_util.Schema`を利用しています．

### schemaを見ていく

先ほどmain.goで使用していた`graphql_util.Schema`を見ていきます．

```golang:application/graphql_util/Schema.go
package graphql_util

import "github.com/graphql-go/graphql"

// define schema, with our rootQuery and rootMutation
var Schema, _ = graphql.NewSchema(graphql.SchemaConfig{
	Query:    rootQuery,
	Mutation: rootMutation,
})
```

スッキリしてますね．`rootQuery`と`rootMutation`で構築されているようです．

### rootQuery, rootMutationを見ていく


```golang:application/graphql_util/Query.go
package graphql_util

import (
	"github.com/mitubaEX/graphQL_sample/application/graphql_util/fields"
	"github.com/graphql-go/graphql"
)

var rootQuery = graphql.NewObject(graphql.ObjectConfig{
	Name: "RootQuery",
	Fields: graphql.Fields{
		"user":      fields.UserField,
		"userList":  fields.UserListField,
		"event":     fields.EventField,
		"eventList": fields.EventListField,
	},
})
```

```golang:application/graphql_util/Mutation.go
package graphql_util

import (
	"github.com/mitubaEX/graphQL_sample/application/graphql_util/fields"
	"github.com/graphql-go/graphql"
)

var rootMutation = graphql.NewObject(graphql.ObjectConfig{
	Name: "RootMutation",
	Fields: graphql.Fields{
		"createUser":  fields.CreateUserField,
		"createEvent": fields.CreateEventField,
	},
})
```

`rootQuery`, `rootMutation`共に，`Fields`という`key`を持っています．これがRESTのリソースの概念に当たる部分です（多分）．試しに`fields.UserField`を見てみましょう．

### fields.UserFieldを見てみる

```golang:application/graphql_util/fields/UserField.go
package fields

import (
	"errors"
	"github.com/mitubaEX/graphQL_sample/application/graphql_util/types"
	"github.com/mitubaEX/graphQL_sample/domain/model/user"
	"github.com/mitubaEX/graphQL_sample/domain/service"
	"github.com/mitubaEX/graphQL_sample/infrastructure"
	"github.com/graphql-go/graphql"
)

// fetch single user
var UserField = &graphql.Field{
	Type:        types.UserType, // 返り値の型
	Description: "Get single user",
	Args: graphql.FieldConfigArgument{ //引数の定義
		"id": &graphql.ArgumentConfig{
			Type: graphql.String,
		},
	},
	Resolve: func(params graphql.ResolveParams) (interface{}, error) { //実行関数
		userId, isOK := params.Args["id"].(string) // 引数取り出し
		if isOK {
			return service.FindUserById(userId)
		}

		return nil, errors.New("no userId")
	},
}

// fetch all user
var UserListField = &graphql.Field{
	Type:        graphql.NewList(types.UserType),
	Description: "List of users",
	Resolve: func(p graphql.ResolveParams) (interface{}, error) {
		return infrastructure.NewUserRepository().UserList(), nil
	},
}

// create user
var CreateUserField = &graphql.Field{
	Type:        types.UserType,
	Description: "Create new user",
	Args: graphql.FieldConfigArgument{
		"userName": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"description": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"photoURL": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"email": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
	},
	Resolve: func(params graphql.ResolveParams) (interface{}, error) {
		userName, _ := params.Args["userName"].(string)
		description, _ := params.Args["description"].(string)
		photoURL, _ := params.Args["photoURL"].(string)
		email, _ := params.Args["email"].(string)

		newUser, err := user.NewUser(userName, description, photoURL, email)
		if err != nil {
			panic(err)
		}
		infrastructure.NewUserRepository().Store(newUser)
		return newUser, nil
	},
}
```

いろいろ書いてますが，まず`UserField`を見てみると，`key`が`Type`と書かれている所に返り値の型が書かれています．そして`Args`という`key`の`value`に引数でもらってくる変数が書いています．返り値や引数という概念があるので，クエリは以下のように書けます．

```
{
  user(id: "1") {// 中身は何かしらの欲しいkeyを指定}
}
```
まるで**関数**ですね．そして最後に`Resolve`で実際に行う処理の内容を書きます．
処理内容は，`params.Args["id"]`で引数の値を受け取り，その`id`で検索をして結果を返すだけです．

こんな感じでどんどんリソースを増やしていってもエンドポイントは一つ（多分一つ）なので，多人数での開発でも大量のエンドポイントとリソースのドキュメントの共有などしなくて済みそうかなぁと思ったりしました．

## データ追加について

GraphQLでデータを追加する時は，`mutation`というキーワードをクエリの前に追加します．
先ほどの`application/graphql_util/fields/UserField.go`の`CreateUserField`は，`rootMutation`の方で使用されています．👇のような感じです．

```golang
var rootMutation = graphql.NewObject(graphql.ObjectConfig{
	Name: "RootMutation",
	Fields: graphql.Fields{
		"createUser":  fields.CreateUserField, // ここ
		"createEvent": fields.CreateEventField,
	},
})
```

`CreateUserField`は👇のような感じです．

```golang:application/graphql_util/fields/UserField.go
// create user
var CreateUserField = &graphql.Field{
	Type:        types.UserType, // 返り値の型
	Description: "Create new user",
	Args: graphql.FieldConfigArgument{ // 引数の定義
		"userName": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"description": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"photoURL": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
		"email": &graphql.ArgumentConfig{
			Type: graphql.NewNonNull(graphql.String),
		},
	},
	Resolve: func(params graphql.ResolveParams) (interface{}, error) {
		userName, _ := params.Args["userName"].(string)
		description, _ := params.Args["description"].(string)
		photoURL, _ := params.Args["photoURL"].(string)
		email, _ := params.Args["email"].(string)

		newUser, err := user.NewUser(userName, description, photoURL, email)
		if err != nil {
			panic(err)
		}
		infrastructure.NewUserRepository().Store(newUser)
		return newUser, nil
	},
}
```

処理内容は引数で値を受け取ってきて，`NewUser`を呼んで新しい`User`を作成し，その`User`を返すだけです．この`CreateUserField`を実際にクエリで実行する時は👇のように書きます．

```
mutation{
  createUser(userName:"mituba", description: "des", photoURL: "photo", email: "email"){
    userId, userName, description, photoURL, email
  }
}
```

`mutation`というキーワードを先頭につけただけです．値の変更のクエリは`mutation`を用いて，発行しましょう．

## graphql-go/graphqlで起こったつらみ

引数にString型などを与える場合は問題なかったのですが，独自の型（**User型など**)を渡そうとしたら**nil**になってしまう問題が起こりました．👇の感じでクエリを投げたかった．

```
mutation{
  createEvent(
    user: {
      userId: "1", userName:"mituba", description: "des", photoURL: "photo", email: "email"
    }, 
    eventName: "event", 
    description: "des", 
    location: "loc", 
    startTime: "start", 
    endTime: "end"){
      eventId, user{userName}
    }
}
```

issueを探したらそれっぽいのがあった．

- [該当しそうなissue](https://github.com/graphql-go/graphql/issues/234)

### 解決策

`NewObject`を使用して返り値用の型を作成して，`NewInputObject`を使用して引数用の型を作成したらいけるらしい．

```golang
// 返り値用
var UserType = graphql.NewObject(graphql.ObjectConfig{
	Name: "User",
	Fields: graphql.Fields{
		"userId": &graphql.Field{
			Type: graphql.String,
		},
		"userName": &graphql.Field{
			Type: graphql.String,
		},
		"description": &graphql.Field{
			Type: graphql.String,
		},
		"photoURL": &graphql.Field{
			Type: graphql.String,
		},
		"email": &graphql.Field{
			Type: graphql.String,
		},
	},
})

// 引数用
var UserInput = graphql.NewInputObject(graphql.InputObjectConfig{
	Name: "user",
	Fields: graphql.InputObjectConfigFieldMap{
		"userId": &graphql.InputObjectFieldConfig{
			Type: graphql.String,
		},
		"userName": &graphql.InputObjectFieldConfig{
			Type: graphql.String,
		},
		"description": &graphql.InputObjectFieldConfig{
			Type: graphql.String,
		},
		"photoURL": &graphql.InputObjectFieldConfig{
			Type: graphql.String,
		},
		"email": &graphql.InputObjectFieldConfig{
			Type: graphql.String,
		},
	},
	Description: "user input type",
})

```

他に良い方法があれば，ご助言いただけると助かります．

## まとめ

とりあえず雑にですが，golangでGraphQLの素振りを行いました．
個人的に書いてて楽しいgolangとエンドポイント生やさなくてもたくさんリソースの処理が書けるGraphQLを今後も触っていきたいと思います．