gqlgen
gqlgen
は Go の GraphQL ライブラリで、GraphQL をインタフェースとして持つ API サーバを Go で構築できる。
gqlgen
はコード上に GraphQL スキーマをガシガシ書いていくライブラリとは異なり、
GraphQL ファイルに記述するスキーマ情報からコードを自動生成する。
なのでコードがごちゃごちゃしにくく、Go を知らない人でも GraphQL スキーマを書ける人なら誰でも定義を編集できるメリットがある(と個人的に考えている)。
デメリットとしては、自動生成されるコードに関してはブラックボックスになってしまうということと、
読み解いて編集したとてスキーマ更新時に再編集しなければいけないところだろうか。
Usage
プロジェクトの作成
公式のチュートリアルがあり一回通してみたが、これとは別に自分なりにプロジェクトを再構築した。
$ mkdir tmp-gqlgen; cd tmp-gqlgen
$ go mod init (Gov1.11以上であれば)
まずはパッケージを入手。
$ go get github.com/99designs/gqlgen
次にプロジェクトルートに GraphQL スキーマファイルを用意。
schema.graphql
type Query {
user(id: ID): User!
pet(id: ID): Pet!
}
type User {
id: ID
name: String
}
type Pet {
id: ID
name: String
}
次に gqlgen.yml
を用意する。
このファイルは gqlgen
でコードを自動生成する際に必要になる。
# GraphQLスキーマファイルの場所
schema:
- ./*.graphql
# スキーマGo実装ファイルの生成場所
exec:
filename: graph/generated/generated.go
package: generated
# モデル構造体ファイルの生成場所
model:
filename: graph/model/models_gen.go
package: model
# resolver(GraphQL版controller的なもの)ファイルの生成場所
resolver:
layout: follow-schema
dir: graph/resolver
package: resolver
# 不足したスキーマ構造体を自動生成する場所
autobind:
- 'github.com/rennnosuke/tmp-gqlgen/graph/model'
gqlgen.yml
が用意できたら、コードの自動生成を実行する。
$ go run github.com/99designs/gqlgen
すると yml で指定したものを含めいくつかコードが生成される。
ファイル名が気に入らなければ、yml 上で変更できる。
tmp-gqlgen
├── go.mod
├── go.sum
├── gqlgen.yml
├── graph
│ ├── generated
│ │ └── generated.go
│ ├── model
│ │ └── models_gen.go
│ └── resolver
│ ├── resolver.go
│ └── schema.resolvers.go
├── schema.graphql
└── server.go
models_gen.go
スキーマに定義した Type
に対応する構造体が定義された。
コメントにあるように、自動生成ファイルはいじらないほうが吉。
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
package model
type Pet struct {
ID *string `json:"id"`
Name *string `json:"name"`
}
type User struct {
ID *string `json:"id"`
Name *string `json:"name"`
}
resolver.go
ベースになる resolver。
resolver は Web アプリケーションアーキテクチャとしての MVC における Controller に近く、endpoint に対応するメソッドを実装している。余談だが、GraphQL モジュール自体薄く保つべきという指針が提唱されているので、Controller 同様多くの処理は持たせず軽い Validation などに留めるのがよい。
package resolver
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct{}
schema.resolver.go
yml の resolver
に指定したスキーマファイル分だけ生成される。今回は schema.graphql
のみ指定したので、 schema.resolver.go
ファイル1つが生成された。
これらの自動生成ファイルで定義される xxxResolver
構造体は resolver.go
の Resolver
構造体をコンポジットする。
QueryResolver
の持つメソッドは GraphQL スキーマクエリのメソッドに対応するが、実装は空(panic)になっているため、独自に編集する必要がある(ので、もちろん編集は可能)。
package resolver
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"fmt"
"github.com/rennnosuke/tmp-gqlgen/graph/generated"
"github.com/rennnosuke/tmp-gqlgen/graph/model"
)
func (r *queryResolver) User(ctx context.Context, id *string) (*model.User, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Pet(ctx context.Context, id *string) (*model.Pet, error) {
panic(fmt.Errorf("not implemented"))
}
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type queryResolver struct{ *Resolver }
スキーマの更新・再生成
一旦初期自動生成後、スキーマの変更を反映したい場合は以下を実行する。
$ go run github.com/99designs/gqlgen
これで resolver
の上書きなしに、 model
や exec
に該当するファイルだけ更新できる。
サーバーの起動
コード自動生成の際、GraphQL API サーバーを起動する server.go
も自動で生成されている。
package main
import (
"log"
"net/http"
"os"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"github.com/rennnosuke/tmp-gqlgen/graph/generated"
"github.com/rennnosuke/tmp-gqlgen/graph/resolver"
)
const defaultPort = "8080"
func main() {
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolver.Resolver{}}))
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
http.Handle("/query", srv)
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
ちなみに、 Server
構造体が Go 標準 net/http
の Handler
インタフェース実装のハンドラ関数 Handler
を持っているため、既存プロジェクトでも net/http
を使用していればすぐに組み込むことができる。
server.go
をそのまま起動すると API サーバーを起動できる。
$ go run server.go
2020/05/17 13:19:52 connect to http://localhost:8080/ for GraphQL playground
Request
実際にクエリを投げられるのを確認するため、Resolver メソッドを書き換える。
構造体のすべてのメンバ型がポインタなので、値を代入するとき少しもどかしい、、、
schema.resolver.go
func (r *queryResolver) User(ctx context.Context, id *string) (*model.User, error) {
name := "Bob"
return &model.User{
ID: id,
Name: &name,
}, nil
}
上記実装後、 go run server.go
でサーバーを再起動すると user()
クエリが投げられるようになる。
Query
{
user(id:"user::1"){
name
}
}
Response
{
"data": {
"user": {
"name": "Bob"
}
}
}
備考
個人の見解だが、自動生成される部分(特に exec
に該当する部分)は殆ど変更の入らない部分なのと、 model
resolver
も自動生成 + 取得処理の外部モジュール化で十分だと思ったので、Go で GraphQL API サーバーを実装する際は gqlgen で良きかな、と思った。
(graphql-goも試したが、Resolver 周りの実装が煩雑だったので心が折れた)