← Back to list

Go gqlgenのAPQを試す

APQ(Automatic persisted queries)を使ったサーバーを実装し、GraphQLクエリ登録の挙動を試す

Takato Fukui · 2024-06-21 12:26 · 0 claps · 6.3 min read
#graphql #gqlgen
Open on Medium ↗

Go gqlgenのAPQを試す

APQ(Automatic persisted queries)を使ったサーバーを実装し、GraphQLクエリ登録の挙動を試す

環境と各バージョン

  • M1 Mac
  • go 1.23.2
  • github.com/99designs/gqlgen v0.17.49

実装

GraphQL定義

type Todo {
  id: ID!
  text: String!
  done: Boolean!
}

type Query {
  todo(id: ID!): Todo!
}

サーバーの実装 handler.NewDefaultServerを使えば 、キャッシュストアにhashicorp/golang-lruを使ったAPQが有効になる だが今回は自前のStructをキャッシュストアを使う またHTTPキャッシュを想定するため、GETメソッドを使うべくtransport.GETを使う

package main

import (
 "context"
 "gra/graph"
 "log"
 "net/http"

 "github.com/99designs/gqlgen/graphql/handler"
 "github.com/99designs/gqlgen/graphql/handler/extension"
 "github.com/99designs/gqlgen/graphql/handler/transport"
)

type cache struct {
 s map[string]any
}

func (c *cache) Get(ctx context.Context, key string) (value any, ok bool) {
 if v, ok := c.s[key]; ok {
  return v, true
 }
 return nil, false
}

func (c *cache) Add(ctx context.Context, key string, value any) {
 c.s[key] = value
}

func main() {
 srv := handler.New(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}}))
 srv.AddTransport(transport.GET{})

 c := &cache{
  s: make(map[string]any),
 }
 srv.Use(extension.AutomaticPersistedQuery{Cache: c})

 http.Handle("/query", srv)

 log.Fatal(http.ListenAndServe(":8080", nil))
}

resolverの実装

package graph

import (
 "context"
 "fmt"
 "gra/graph/model"
)

func (r *queryResolver) Todo(ctx context.Context, id string) (*model.Todo, error) {
 t := map[string]*model.Todo{
  "1": {ID: "1", Text: "洗濯する", Done: false},
  "2": {ID: "2", Text: "料理する", Done: true},
 }

 if todo, ok := t[id]; ok {
  return todo, nil
 }

 return nil, fmt.Errorf("todo not found")
}

func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }

type queryResolver struct{ *Resolver }

リクエストする

リクエストしたいGraphQLクエリをSHA-256で変換する

echo -n 'query findTodo($id: ID!) { todo(id: $id) { text } }' | shasum -a 256

まずはpersistedQueryのみでリクエストする

curl --get http://localhost:8080/query \
  --header 'content-type: application/json' \
  --data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"753d19484462e54ceed5af058741e0d4c57d20eca3d80bbe58d92bfa1b6cec4d"}}' \
  --data-urlencode 'variables={"id": "1"}'

まだGraphQLクエリを登録してないのでエラーが返る

{"errors":[{"message":"PersistedQueryNotFound","extensions":{"code":"PERSISTED_QUERY_NOT_FOUND"}}],"data":null}

GraphQLクエリを加えてリクエストする

curl --get http://localhost:8080/query \
  --header 'content-type: application/json' \
  --data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"753d19484462e54ceed5af058741e0d4c57d20eca3d80bbe58d92bfa1b6cec4d"}}' \
  --data-urlencode 'query=query findTodo($id: ID!) { todo(id: $id) { text } }' \
  --data-urlencode 'variables={"id": "1"}'

結果が返る

{"data":{"todo":{"text":"洗濯する"}}}

再度persistedQueryのみでリクエストすると同じ結果が返る

またvariablesの値を変えても期待した結果が返る

curl --get http://localhost:8080/query \
  --header 'content-type: application/json' \
  --data-urlencode 'extensions={"persistedQuery":{"version":1,"sha256Hash":"753d19484462e54ceed5af058741e0d4c57d20eca3d80bbe58d92bfa1b6cec4d"}}' \
  --data-urlencode 'variables={"id": "2"}'
{"data":{"todo":{"text":"料理する"}}}

クライアントのGraphQLクエリ登録のタイミング

gqlgenの公式リファレンスに記載の通り、まずはPersistedQueryのみでリクエストし、PersistedQueryNotFoundが返ってきたらGraphQLクエリを含めてリクエストすることでクエリ登録する

公式リファレンスではキャッシュストアにRedisを使っているが、わざわざRedisを使わずともlruで良いと思う

所感

導入にあたっては特にクライアントの改修が必要になり、クライアントの複雑さが増す APQはネットワーク転送量削減やCDNキャッシュ促進のモチベーションで使う 特にGraphQLの利点を存分に生かしてないにも関わらずAPQを使うくらいであれば、もはやREST APIにしたほうがいいかもしれない


메타데이터
post_id
fcbeeb2aeec9
slug
go-gqlgenのapqを試す-fcbeeb2aeec9
url
https://medium.com/@eyasy1217/go-gqlgen%E3%81%AEapq%E3%82%92%E8%A9%A6%E3%81%99-fcbeeb2aeec9
canonical_url
https://medium.com/@eyasy1217/go-gqlgen%E3%81%AEapq%E3%82%92%E8%A9%A6%E3%81%99-fcbeeb2aeec9
author_url
https://medium.com/@eyasy1217
status
ok
fetched_at
2026-07-23 14:04:14