gqlgen go,通过添加一个解析器减少数据库调用

gqlgen go, reduce db calls by adding one resolver

我在解决导致性能下降的特定情况时遇到了一些问题。 我很确定这是可以做到的,但我不知道该怎么做。

这是一个暴露问题的示例模式:

type Answer{
    answerId: String!
    text: String!
    topic: Topic!
}

type Topic {
    topicId: String!
    name: String!
    level: Int!
}
extend type Query {
    answer(answerId: String!): Answer!
    answers: [Answer!]! 
}

我已经按照文档进行操作,特别是这部分 https://gqlgen.com/getting-started/#dont-eagerly-fetch-the-user 从我的架构中,它生成以下解析器:

func (r *queryResolver) Answer(ctx context.Context, answerId string) (*models.Answer, error) {
...
#Single Query which retrives single record of Answer from DB.
#Fills a model Answer with the Id and the text
#Proceeds by calling the Topic resolver
...
}

func (r *queryResolver) Answers(ctx context.Context) ([]*models.Answer, error) {
...
#Single Query which retrives list of Answers from DB
#Fills a list of model Answer with the Id and the text
-->#For each element of that list, it calls the Topic resolver
...
}


func (r *answerResolver) Topic(ctx context.Context, obj *models.Answer) (*models.Topic, error) {
...
#Single Query which retrives single record of Topic from DB
#Return a model Topic with id, name and level
...
}

当使用 answerId 参数调用 answer 查询时,answer 解析器被触发,它解析 text 属性 并调用 Topic解析器。 Topic 解析器按预期工作,检索 Topic 并将其合并到 Answer 和 return.

answers 查询在没有 answerId 参数的情况下被调用时,answer 解析器被触发,它通过单个查询检索 answers 的列表。 然后,对于该列表的每个元素,它调用 Topic 解析器。 Topic 检索 Topic 并将其合并到单个 Answer 和 return.

两种情况下的结果都不错,但如果我要求很多答案,answers 查询会成为性能问题。 对于每个答案,Topic 解析器都会被触发并执行查询以检索单个记录。

例如。如果我有 2 个答案 --> 1 个 [Answer0, Answer1] 查询,然后 1 个 Topic0 查询和 1 个 Topic1

查询

例如。 10 个答案 --> [Answer0, ..., Answer9] 1 个,然后每个 TopicN

10 个

我想获得一些 topic 数组解析器,例如


func (r *answersResolver) Topics(ctx context.Context, obj *[]models.Answer) (*[]models.Topic, error) {
...
#Single Query which retrives list of Topics from DB
#Return a list of model Topic with id, name and level
...
}

而且我希望 returned 数组的每个元素都与 Answers 数组的相应元素合并。

有可能吗?我在哪里可以找到这种方法的例子? 谢谢

问题可以使用数据加载器解决(docs)

我必须为 Topics 实现以下数据源:

package dataloader

import (
    "github.com/graph-gophers/dataloader"
)

type ctxKey string

const (
    loadersKey = ctxKey("dataloaders")
)


type TopicReader struct {
    conn *sql.DB
}

func (t *TopicReader) GetTopics(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
    topicIDs := make([]string, len(keys))
    for ix, key := range keys {
        topicIDs[ix] = key.String()
    }
    res := u.db.Exec(
        r.Conn,
        "SELECT id, name, level
        FROM topics
        WHERE id IN (?" + strings.Repeat(",?", len(topicIDs-1)) + ")",
        topicIDs...,
    )
    defer res.Close()

    output := make([]*dataloader.Result, len(keys))
    for index, _ := range keys {
            output[index] = &dataloader.Result{Data: res[index], Error: nil}
    }
    return output
}

type Loaders struct {
    TopicLoader *dataloader.Loader
}


func NewLoaders(conn *sql.DB) *Loaders {
    topicReader := &TopicReader{conn: conn}
    loaders := &Loaders{
        TopicLoader: dataloader.NewBatchedLoader(t.GetTopics),
    }
    return loaders
}

func Middleware(loaders *Loaders, next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        nextCtx := context.WithValue(r.Context(), loadersKey, loaders)
        r = r.WithContext(nextCtx)
        next.ServeHTTP(w, r)
    })
}

func For(ctx context.Context) *Loaders {
    return ctx.Value(loadersKey).(*Loaders)
}

func GetTopic(ctx context.Context, topicID string) (*model.Topic, error) {
    loaders := For(ctx)
    thunk := loaders.TopicLoader.Load(ctx, dataloader.StringKey(topicID))
    result, err := thunk()
    if err != nil {
        return nil, err
    }
    return result.(*model.Topic), nil
}