Yamashouは田舎に行きたい

ある田舎県の田舎の町で大学生やってた僕が、思ったこと、やったこと、勉強したこと、などをつらつら書いて行く

GraphQLのGoクライアントを作ってみた

普段GAE/Goでgqlgenを使ってAPIサーバーを開発をしていて、WebクライアントはApolloGraphQL Code Generatorを使用して開発をしています。 そこで、APIサーバー側のE2Eテストを実現するために、Go言語のGraphQLクライアントが必要になりました。その時の選択肢は以下のような選択肢があります。

この二つのクライアントは非常にシンプルな作りで、GraphQLのAPIを呼び出す分には十分な作りでした。しかし、GraphQLを使ったことある方ならわかるとは思いますが、インプットの型とレスポンスの型が非常に多くなるのと、ネストが深くなります。そうなると、クライアントのQuery毎にレスポンスの型を用意する必要があります。そして、サーバーの開発はgqlgenを使っているので、resolverまでが自動生成される気持ちよさを覚えてしまっているので、GraphQL Code Generatorと同じように定義したQuery毎に型を生成して欲しいという欲望に駆られてしまいました。そして自社のプロダクト開発では、そのクライアント生成を自作して半年ほど使っております。 しかし、今回個人的に、GoでGraphQLのAPIを叩きたい時がきたので、gqlgenのサーバーに大してのみ生成していたものをIntrospection QueryでクライアントのSchema定義から型を生成するようにしたものを公開いたしました。

github.com

Annict GraphQL APIでクライアントを生成

今回叩きたかったAPIAnnict GraphQL APIです。 そして、こちらの使い方を説明されている(Qiitaの記事)https://qiita.com/shimbaco/items/e3f2f8650b08e1e060bdのクライアントを生成してみたいと思います。

まず、gqlgencを入れます。

go get -u github.com/Yamashou/gqlgenc

次に、以下の記事よりAnnictアカウントの設定やアクセストークンを取得してください

qiita.com

取得したアクセストークンを環境変数として設定します。

export ANNICT_KEY=<アクセストークン>

今回こちらのクライアントは基本的にgqlgenのPlugin機構を使っているので、設定もyaml方式を取っているので、.gqlgenc.yamlを用意します。

model:
  filename: ./gen/models_gen.go # Schema定義から各オブジェクトを生成するファイルを指定
client:
  filename: ./gen/client.go # クライアントの実装を生成するファイルを指定
models: # Schema側で指定している型をGoではどの型にするかなどを指定できます
  Int:
    model: github.com/99designs/gqlgen/graphql.Int64
  Date:
    model: github.com/99designs/gqlgen/graphql.Time
endpoint:
  url: https://api.annict.com/graphql # APIエンドポイントを指定します
  headers: # Introspection Queryを投げるのにヘッダーが必要な場合は設定します。
    Authorization: "Bearer ${ANNICT_KEY}"  # 今回はAnnictのアクセストークンが必要なので、環境変数として、セットしたものをそのままこのように記述して使うことができます
query:
  - "./query/*.graphql" # リクエストで使うqueryの定義ファイルを指定します

それでは/query以下に今回使うqueryを定義していきます。

mkdir query

query/query.grahql

query GetUserName {
    viewer {
        username
        name
    }
}


query SearchWorksBySeasons($seasons: [String!], $first: Int!) {
    searchWorks(seasons: $seasons, first: $first) {
        edges {
            node {
                title
                watchersCount
            }
        }
    }
}

そうしたら、.gqlgenc.yamlのあるディレクトリで、gqlgencを実行します。すると指定した通り/genに型定義とクライアント、リクエストのインプットとアウトプットの型が生成されます。

それでは、そのクライアントを使ってリクエストを送ってみたいと思います。

main.go

func NewAnnictClient(c *client.Client) *gen.Client {
    return &gen.Client{Client: c}
}


func main() {
    key := os.Getenv("ANNICT_KEY")
    authHeader := func(req *http.Request) {
        req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", key))
    }

    ctx := context.Background()

    annictClient := NewAnnictClient(client.NewClient(http.DefaultClient, "https://api.annict.com/graphql", authHeader))
    userName, err := annictClient.GetUserName(ctx)
    if err != nil {
        fmt.Fprintf(os.Stderr, err.Error())
        os.Exit(1)
    }

    fmt.Println(userName.Viewer.Name, userName.Viewer.Username)


    works, err := annictClient.SearchWorksBySeasons(ctx, []string{"2017-spring"}, 3)
    if err != nil {
        fmt.Fprintf(os.Stderr, err.Error())
        os.Exit(1)
    }

    for _, work := range works.SearchWorks.Edges {
        fmt.Println(work.Node.Title, work.Node.WatchersCount)
    }

}

gqlgenのpluginとして使う

またこのクライアントの実装はgqlgenのPluginのConfigMutatorを実装しているため, github.com/Yamashou/gqlgenc/clientgenのパッケージをgqlgenのこちらの記事にしたがってインポートすることで、そのサーバーに対応したものを使うことができます。

その他機能について、

GraphQLのスペックについて

GraphQLのQuery, Mutation, Fragmentなど、最低限必要なものは使うことが可能ですが、Subscriptionやdirectiveには追従できていない部分もありますし、何がバグで動かないなどの修正が必要ならissueを投げてもらえると嬉しいです。

Schemaのロード方法について

現在はエンドポイントに大して、Introspection Queryを叩いて、そこからロードしているので、もしSchema.jsonやSchemaファイルからロードする方法を用意してはいません。ので、もし必要ならIssueをお願いします。

最後に

最初にも言いましたが、gqlgenを使用して普段は開発をしています。もっとこのツールを使って開発してくれる人が増えてくれると嬉しいので、ぜひこの記事を読んでいただいた方にはgqlgenを一度使ってみてもらえると嬉しいです。そして、そこでクライアントの悩みが出たら、こちらのgqlgencを使ってみてください。

参考

gqlgen.com

graphql-code-generator.com

github.com

github.com

annict.com

docs.annict.com