仕事で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の仕組みやメンタルモデルはまだうまく学べていないので,これからの課題としたいですね。