仕事でGraphQLを使う機会があり,言語はGoなので,gqlgenの練習をした。
gqlgen なに
GraphQLのSchemaを与えることでGraphQLのサーバのGoコードを生成してくれるジェネレータツール。
やったこと
チュートリアルをなぞっただけ。
環境構築
Makefileにいろいろ書いたりするいつもの作業。go modの理解がまだ十分でないので,go mod init github.com/windymelt/go-gqlgen-exerciseみたいなのをいきなり入力させられるのに戸惑う。公開前提の世界観なのだろうか。
gqlgen入れる(Schema書く)
入れるといってもgo runすれば勝手に降ってくるので,まずスキーマを書く。schema.graphqlを書く。
type Sixsixsix {
id: ID!
text: String!
}
type Query {
ssss: [Sixsixsix!]!
}
input NewSixsixsix {
id: ID!
text: String!
}
type Mutation {
create666(input: NewSixsixsix!): Sixsixsix!
}
どうやらtype Queryとtype Mutaionは必須らしい,ということがこのへんで分かってくる。実装しながら覚えていくタイプ。
ちゃんと勉強しろという説もあるが,刺激がないと覚えられないタイプなのでこのへんは難しいですね。
で,gqlgenを走らせるといろいろ生成される。
go run github.com/99designs/gqlgen -v
windymelt% tree . ├── Makefile ├── generated.go ├── go.mod ├── go.sum ├── gqlgen.yml ├── main.go ├── models_gen.go ├── resolver.go └── schema.graphql
自分は事前にmain.goを書いていたのでこういうディレクトリ構造になった。なにか色々と生成されているが,生成されたコードまわりは分けたい。goのパッケージまわりとか勉強したらちゃんと分けられるようになりそう。
main.go書く1
ひとまず動いた感を出したい。gqlgenはplaygroundsというブラウザ上でGraphQLを叩けるやつも提供しているので,ひとまずこれを動かす。
// main.go package main import ( "log" "net/http" "github.com/99designs/gqlgen/handler" ) func main() { http.Handle("/", handler.Playground("GraphQL Playground", "/query")) log.Print("listening on http://localhost:8000") log.Fatal(http.ListenAndServe(":8000", nil)) }
これでビルドして動かすとlocalhost:8000で以下のような画面が表示できるようになる。

resolver実装する
このままだと何も実装されていないのでクエリを叩いてもサーバは死んでしまう。実際に飛んできたクエリをいい感じに処理して(表現が雑すぎる)データを返してくれる箇所を実装する必要があって,これをresolverという。
gqlgenは適当に(panicとかで)穴が開いたresolverをresolver.goに吐出するので,ここを実装して穴埋めをすればよい。
package main import ( "context" "fmt" "math/rand" ) // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES. type Resolver struct{ ssss []*Sixsixsix } func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} } func (r *Resolver) Query() QueryResolver { return &queryResolver{r} } type mutationResolver struct{ *Resolver } // ↓ここ実装した func (r *mutationResolver) Create666(ctx context.Context, input NewSixsixsix) (*Sixsixsix, error) { s := &Sixsixsix{ Text: input.Text, ID: fmt.Sprintf("T%d", rand.Int()), } r.ssss = append(r.ssss, s) return s, nil } type queryResolver struct{ *Resolver } // ↓ここ実装した func (r *queryResolver) Ssss(ctx context.Context) ([]*Sixsixsix, error) { return r.ssss, nil }
ほぼチュートリアルのコピペだが,なんとなく雰囲気はわかる。なんらかのコンテキストが渡ってくるので,それを元にデータを引いてきたり,追加したりすればよいらしい。
とはいえ今回は適当にオンメモリでデータを差し込んだり引いたりするので,コンテキストを読む必要がない。適当にResolverのフィールドに値を突っ込む(なんて無作法な!と思ったあなた,これはチュートリアルにもそう書いてある。)
やっていることはといえば,渡ってきたtextフィールドとランダムに生成したidを使ってデータを追加したり,contextを完全無視して配列の中身を全部返したりしている。
main.go書く2
以下の行をいい感じに追加する。
http.Handle("/query", handler.GraphQL(NewExecutableSchema(Config{Resolvers: &Resolver{}})))
するとデータを作成できるようになる。データ作成もMutationの範疇に入るらしい。

データをQueryすることもできるようになった。

とはいえパラメータをつけてQueryとかはできない。あとはコードをいじっていったら色々なQueryやMutationができるようになりそう。
結語
gqlgenのさわりだけ練習し,基礎を学ぶことができました。GraphQLについてもっと勉強して,より基本的な実装に進めるようになりたいですね。- Go modulesの仕組みやメンタルモデルはまだうまく学べていないので,これからの課題としたいですね。