GraphQL e Golang

A tecnologia GraphQL nos últimos anos, depois que a empresa Facebook a transferiu para a categoria de código aberto, tornou-se muito popular. O autor do material, cuja tradução publicamos hoje, diz que tentou trabalhar com o GraphQL no Node.js e, por experiência própria, estava convencido de que essa tecnologia, graças às suas notáveis ​​capacidades e simplicidade, não atrai acidentalmente tanta atenção. Recentemente, enquanto estava envolvido em um novo projeto, ele mudou do Node.js para Golang. Então, ele decidiu testar a colaboração de Golang e GraphQL.



Informações preliminares


Você pode aprender com a definição oficial do GraphQL que essa é uma linguagem de consulta para a API e um tempo de execução para executar essas consultas em dados existentes. O GraphQL fornece uma descrição completa e compreensível dos dados em uma determinada API, permite que os clientes solicitem exatamente as informações necessárias e nada mais, simplifica o desenvolvimento da API ao longo do tempo e oferece ferramentas poderosas aos desenvolvedores.

Não há muitas bibliotecas GraphQL para Golang. Em particular, tentei bibliotecas como Thunder , graphql , graphql-go e gqlgen . Devo observar que o melhor de tudo que tentei foi a biblioteca gqlgen.

A biblioteca gqlgen ainda está na versão beta, no momento em que este material foi escrito, era a versão 0.7.2 . A biblioteca está evoluindo rapidamente. Aqui você pode descobrir os planos para seu desenvolvimento. Agora, o patrocinador oficial do gqlgen é o projeto 99designs , o que significa que essa biblioteca, possivelmente, se desenvolverá ainda mais rápido do que antes. Os principais desenvolvedores desta biblioteca são vektah e neelance , enquanto neelance, além disso, trabalha na biblioteca graphql-go.

Vamos falar sobre a biblioteca gqlgen com base no pressuposto de que você já possui conhecimento básico do GraphQL.

Recursos do Gqlgen


Na descrição do gqlgen, você pode descobrir o que temos diante de nós é uma biblioteca para criar rapidamente servidores GraphQL estritamente tipificados no Golang. Essa frase me parece muito promissora, pois significa que, ao trabalhar com esta biblioteca, não encontrarei algo como a map[string]interface{} , pois uma abordagem baseada em digitação estrita é usada aqui.

Além disso, esta biblioteca usa uma abordagem baseada em um esquema de dados. Isso significa que as APIs são descritas usando a Linguagem de definição de esquema do GraphQL. Essa linguagem possui suas próprias e poderosas ferramentas de geração de código que criam automaticamente o código GraphQL. Nesse caso, o programador pode implementar apenas a lógica básica dos métodos de interface correspondentes.

Este artigo está dividido em duas partes. O primeiro é dedicado aos métodos básicos de trabalho e o segundo aos avançados.

Os principais métodos de trabalho: configuração, solicitações para recebimento e alteração de dados, assinaturas


Como aplicativo experimental, usaremos um site no qual os usuários podem publicar vídeos, adicionar capturas de tela e críticas, pesquisar vídeos e exibir listas de registros associados a outros registros. Vamos começar a trabalhar neste projeto:

 mkdir -p $GOPATH/src/github.com/ridhamtarpara/go-graphql-demo/ 

Crie o seguinte arquivo de esquema de dados ( schema.graphql ) no diretório raiz do projeto:

 type User {   id: ID!   name: String!   email: String! } type Video {   id: ID!   name: String!   description: String!   user: User!   url: String!   createdAt: Timestamp!   screenshots: [Screenshot]   related(limit: Int = 25, offset: Int = 0): [Video!]! } type Screenshot {   id: ID!   videoId: ID!   url: String! } input NewVideo {   name: String!   description: String!   userId: ID!   url: String! } type Mutation {   createVideo(input: NewVideo!): Video! } type Query {   Videos(limit: Int = 25, offset: Int = 0): [Video!]! } scalar Timestamp 

Ele descreve os modelos de dados básicos, uma mutação ( Mutation , descrição da solicitação de alteração de dados), usada para publicar novos arquivos de vídeo no site, e uma consulta ( Query ) para obter uma lista de todos os arquivos de vídeo. Leia mais sobre o esquema GraphQL aqui . Além disso, aqui declaramos um de nossos próprios tipos de dados escalares. Não estamos satisfeitos com os 5 tipos de dados escalares padrão ( Int , Float , String , Boolean e ID ) que estão no GraphQL.

Se você precisar usar seus próprios tipos, poderá declará-los em schema.graphql (no nosso caso, esse tipo é Timestamp ) e fornecer suas definições no código. Ao usar a biblioteca gqlgen, você precisa fornecer métodos para empacotamento e desempacotamento para todos os seus próprios tipos escalares e configurar o mapeamento usando gqlgen.yml .

Note-se que na versão mais recente da biblioteca houve uma mudança importante. Nomeadamente, a dependência dos arquivos binários compilados foi removida. Portanto, o arquivo scripts/gqlgen.go deve ser adicionado ao projeto scripts/gqlgen.go seguinte conteúdo:

 // +build ignore package main import "github.com/99designs/gqlgen/cmd" func main() { cmd.Execute() } 

Depois disso, você precisa inicializar o dep :

 dep init 

Agora é hora de aproveitar os recursos de geração de código da biblioteca. Eles permitem que você crie todo o código clichê chato, que, no entanto, não pode ser chamado de completamente desinteressante. Para iniciar o mecanismo de geração automática de código, execute o seguinte comando:

 go run scripts/gqlgen.go init 

Como resultado de sua execução, os seguintes arquivos serão criados:

  • gqlgen.yml : arquivo de configuração para gerenciar a geração de código.
  • generated.go : código gerado.
  • models_gen.go : todos os modelos e tipos de dados do esquema fornecido.
  • resolver.go : aqui será o código que o programador cria.
  • server/server.go : ponto de entrada com http.Handler para iniciar o servidor GraphQL.

Dê uma olhada no modelo gerado para o tipo de Video (arquivo generated_video.go ):

 type Video struct { ID          string  `json:"id"` Name        string  `json:"name"` User        User  `json:"user"` URL         string  `json:"url"` CreatedAt   string  `json:"createdAt"` Screenshots []*Screenshot `json:"screenshots"` Related     []Video  `json:"related"` } 

Aqui você pode ver que o ID é uma string, CreatedAt também é uma string. Outros modelos relacionados são configurados de acordo. No entanto, em aplicações reais, isso não é necessário. Se você estiver usando qualquer tipo de dados SQL, precisará, por exemplo, que o campo ID seja, dependendo do banco de dados usado, um int64 int ou int64 .

Por exemplo, eu uso o PostgreSQL neste aplicativo de demonstração, então é claro que preciso que o campo ID seja do tipo int e CreatedAt tipo time.Time . Isso leva ao fato de que precisamos definir nosso próprio modelo e informar ao gqlgen que precisamos usar nosso modelo em vez de gerar um novo. Aqui está o conteúdo do arquivo models.go :

 type Video struct { ID          int `json:"id"` Name        string `json:"name"` Description string    `json:"description"` User        User `json:"user"` URL         string `json:"url"` CreatedAt   time.Time `json:"createdAt"` Related     []Video } //    int  ID func MarshalID(id int) graphql.Marshaler { return graphql.WriterFunc(func(w io.Writer) {   io.WriteString(w, strconv.Quote(fmt.Sprintf("%d", id))) }) } //        func UnmarshalID(v interface{}) (int, error) { id, ok := v.(string) if !ok {   return 0, fmt.Errorf("ids must be strings") } i, e := strconv.Atoi(id) return int(i), e } func MarshalTimestamp(t time.Time) graphql.Marshaler { timestamp := t.Unix() * 1000 return graphql.WriterFunc(func(w io.Writer) {   io.WriteString(w, strconv.FormatInt(timestamp, 10)) }) } func UnmarshalTimestamp(v interface{}) (time.Time, error) { if tmpStr, ok := v.(int); ok {   return time.Unix(int64(tmpStr), 0), nil } return time.Time{}, errors.TimeStampError } 

Dizemos à biblioteca que ele deve usar esses modelos (arquivo gqlgen.yml ):

 schema: - schema.graphql exec: filename: generated.go model: filename: models_gen.go resolver: filename: resolver.go type: Resolver models: Video:   model: github.com/ridhamtarpara/go-graphql-demo/api.Video ID:   model: github.com/ridhamtarpara/go-graphql-demo/api.ID Timestamp:   model: github.com/ridhamtarpara/go-graphql-demo/api.Timestamp 

O ponto de tudo isso é que agora temos nossas próprias definições para ID e Timestamp com métodos para empacotamento e desempacotamento e mapeamento deles no arquivo gqlgen.yml . Agora que o usuário fornece a sequência como um ID , o método UnmarshalID() converte essa sequência em um número inteiro. Ao enviar uma resposta, o método MarshalID() converte o número em uma string. O mesmo acontece com o Timestamp / Timestamp ou com qualquer outro tipo de escalar declarado pelo programador.

Agora é hora de implementar a lógica do aplicativo. Abra o arquivo resolver.go e adicione descrições de mutações e consultas nele. Já existe um código clichê gerado automaticamente que precisamos preencher com significado. Aqui está o código para este arquivo:

 func (r *mutationResolver) CreateVideo(ctx context.Context, input NewVideo) (api.Video, error) { newVideo := api.Video{   URL:         input.URL,   Name:        input.Name,   CreatedAt:   time.Now().UTC(), } rows, err := dal.LogAndQuery(r.db, "INSERT INTO videos (name, url, user_id, created_at) VALUES($1, $2, $3, $4) RETURNING id",   input.Name, input.URL, input.UserID, newVideo.CreatedAt) defer rows.Close() if err != nil || !rows.Next() {   return api.Video{}, err } if err := rows.Scan(&newVideo.ID); err != nil {   errors.DebugPrintf(err)   if errors.IsForeignKeyError(err) {     return api.Video{}, errors.UserNotExist   }   return api.Video{}, errors.InternalServerError } return newVideo, nil } func (r *queryResolver) Videos(ctx context.Context, limit *int, offset *int) ([]api.Video, error) { var video api.Video var videos []api.Video rows, err := dal.LogAndQuery(r.db, "SELECT id, name, url, created_at, user_id FROM videos ORDER BY created_at desc limit $1 offset $2", limit, offset) defer rows.Close();   if err != nil {   errors.DebugPrintf(err)   return nil, errors.InternalServerError } for rows.Next() {   if err := rows.Scan(&video.ID, &video.Name, &video.URL, &video.CreatedAt, &video.UserID); err != nil {     errors.DebugPrintf(err)     return nil, errors.InternalServerError   }   videos = append(videos, video) } return videos, nil } 

Agora vamos testar a mutação.

Mutação createVideo

Isso funciona! Mas por que não há nada nas informações do usuário (objeto do user )? Ao trabalhar com o GraphQL, são aplicáveis ​​conceitos semelhantes ao carregamento “preguiçoso” (preguiçoso) e “ganancioso” (ansioso). Como esse sistema é extensível, você precisa especificar quais campos precisam ser preenchidos "avidamente" e quais são "preguiçosos".

Sugeri à equipe da organização onde trabalho a seguinte “regra de ouro” que se aplica ao trabalhar com o gqlgen: “Não inclua no modelo os campos que precisam ser carregados apenas se solicitados pelo cliente.”

No nosso caso, eu preciso baixar dados sobre clipes de vídeo relacionados (e até mesmo informações do usuário) apenas se o cliente solicitar esses campos. Mas como incluímos esses campos no modelo, o gqlgen assume que fornecemos esses dados recebendo informações sobre o vídeo. Como resultado, agora temos estruturas vazias.

Às vezes, acontece que um determinado tipo de dados é necessário sempre, portanto, é impraticável fazer o download usando uma solicitação separada. Para isso, para melhorar o desempenho, você pode usar algo como junções SQL. Uma vez (no entanto, isso não se aplica ao exemplo considerado aqui), eu precisava enviar seus metadados junto com o vídeo. Essas entidades foram armazenadas em locais diferentes. Como resultado, se meu sistema recebeu uma solicitação para baixar um vídeo, tive que fazer outra solicitação para obter metadados. Mas, como eu sabia sobre esse requisito (isto é, eu sabia que no lado do cliente sempre precisava do vídeo e de seus metadados), preferi usar a técnica de download guloso para melhorar o desempenho.

Vamos reescrever o modelo e gerar o código gqlgen novamente. Para não complicar a história, escrevemos apenas métodos para o campo do user (arquivo models.go ):

 type Video struct { ID          int `json:"id"` Name        string `json:"name"` Description string    `json:"description"` UserID      int `json:"-"` URL         string `json:"url"` CreatedAt   time.Time `json:"createdAt"` } 

Adicionamos um UserID e removemos a estrutura do User . Agora gere novamente o código:

 go run scripts/gqlgen.go -v 

Graças a este comando, os seguintes métodos de interface serão criados para resolver estruturas indefinidas. Além disso, você precisará determinar o seguinte no resolvedor (arquivo gerado.go):

 type VideoResolver interface { User(ctx context.Context, obj *api.Video) (api.User, error) Screenshots(ctx context.Context, obj *api.Video) ([]*api.Screenshot, error) Related(ctx context.Context, obj *api.Video, limit *int, offset *int) ([]api.Video, error) } 

Aqui está a definição (arquivo resolver.go ):

 func (r *videoResolver) User(ctx context.Context, obj *api.Video) (api.User, error) { rows, _ := dal.LogAndQuery(r.db,"SELECT id, name, email FROM users where id = $1", obj.UserID) defer rows.Close() if !rows.Next() {   return api.User{}, nil } var user api.User if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {   errors.DebugPrintf(err)   return api.User{}, errors.InternalServerError } return user, nil } 

Agora, os resultados do teste de mutação terão a aparência mostrada abaixo.


Mutação createVideo

O que acabamos de discutir são os fundamentos do GraphQL, tendo dominado quais, você já pode escrever algo de sua preferência. No entanto, antes de mergulhar em experimentos com o GraphQL e Golang, será útil falar sobre assinaturas, diretamente relacionadas ao que estamos fazendo aqui.

▍ Assinaturas


O GraphQL fornece a capacidade de assinar alterações de dados que ocorrem em tempo real. A biblioteca gqlgen permite, em tempo real, usando soquetes da web, trabalhar com eventos de assinatura.

A assinatura precisa ser descrita no arquivo schema.graphql . Aqui está a descrição da assinatura do evento de publicação de vídeo:

 type Subscription {   videoPublished: Video! } 

Agora, execute a geração automática de código novamente:

 go run scripts/gqlgen.go -v 

Como já mencionado, durante a criação automática de código no arquivo generated.go , é criada uma interface que deve ser implementada no reconhecedor. No nosso caso, fica assim (arquivo resolver.go ):

 var videoPublishedChannel map[string]chan api.Video func init() { videoPublishedChannel = map[string]chan api.Video{} } type subscriptionResolver struct{ *Resolver } func (r *subscriptionResolver) VideoPublished(ctx context.Context) (<-chan api.Video, error) { id := randx.String(8) videoEvent := make(chan api.Video, 1) go func() {   <-ctx.Done() }() videoPublishedChannel[id] = videoEvent return videoEvent, nil } func (r *mutationResolver) CreateVideo(ctx context.Context, input NewVideo) (api.Video, error) { //   ... for _, observer := range videoPublishedChannel {   observer <- newVideo } return newVideo, nil } 

Agora, ao criar um novo vídeo, você precisa acionar um evento. No nosso exemplo, isso é feito na linha for _, observer := range videoPublishedChannel .

Agora é hora de verificar sua assinatura.


Verificar assinatura

O GraphQL, é claro, possui certos recursos valiosos, mas como eles dizem, nem tudo o que reluz é ouro. Ou seja, estamos falando do fato de que alguém que usa o GraphQL precisa cuidar da autorização, da complexidade das solicitações, do cache, do problema das solicitações N + 1, da limitação da velocidade de execução da consulta e de outras coisas. Caso contrário, um sistema desenvolvido usando o GraphQL pode enfrentar uma queda séria no desempenho.

Técnicas avançadas: autenticação, carregadores de dados, complexidade de consultas


Toda vez que leio manuais como esse, sinto que, depois de dominá-los, aprendo tudo o que preciso saber sobre uma determinada tecnologia e tenho a capacidade de resolver problemas de qualquer complexidade.

Porém, quando começo a trabalhar em meus próprios projetos, geralmente encontro situações imprevistas que parecem erros do servidor ou solicitações que estão em execução há muito tempo ou outras situações de conflito. Como resultado, para fazer isso, tenho que me aprofundar no que recentemente pareceu perfeitamente compreensível. Neste mesmo manual, espero que isso possa ser evitado. É por isso que nesta seção examinaremos algumas técnicas avançadas para trabalhar com o GraphQL.

▍ Autenticação


Ao trabalhar com a API REST, temos um sistema de autenticação e ferramentas de autorização padrão ao trabalhar com um determinado terminal. Porém, ao usar o GraphQL, apenas um ponto de extremidade é usado; portanto, as tarefas de autenticação podem ser resolvidas usando diretivas de esquema. Edite o arquivo schema.graphql seguinte maneira:

 type Mutation {   createVideo(input: NewVideo!): Video! @isAuthenticated } directive @isAuthenticated on FIELD_DEFINITION 

Criamos a diretiva isAuthenticated e a createVideo assinatura createVideo . Após a próxima sessão de geração automática de código, você precisa definir uma definição para esta diretiva. Agora, as diretivas são implementadas na forma de métodos de estrutura, e não na forma de interfaces, portanto, precisamos descrevê-las. server.go o código gerado automaticamente localizado no arquivo server.go e criei um método que retorna a configuração do server.go para o arquivo server.go . Aqui está o arquivo resolver.go :

 func NewRootResolvers(db *sql.DB) Config { c := Config{   Resolvers: &Resolver{     db: db,   }, } //   c.Directives.IsAuthenticated = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {   ctxUserID := ctx.Value(UserIDCtxKey)   if ctxUserID != nil {     return next(ctx)   } else {     return nil, errors.UnauthorisedError   } } return c } 

Aqui está o arquivo server.go :

 rootHandler:= dataloaders.DataloaderMiddleware(   db,   handler.GraphQL(     go_graphql_demo.NewExecutableSchema(go_graphql_demo.NewRootResolvers(db)   ) ) http.Handle("/query", auth.AuthMiddleware(rootHandler)) 

Lemos o ID do usuário a partir do contexto. Você não acha isso estranho? Como esse significado entrou no contexto e por que ele apareceu no contexto? O fato é que o gqlgen fornece contextos de solicitação apenas no nível de implementação, portanto, não temos como ler nenhum dado de solicitação HTTP, como cabeçalhos ou cookies, em reconhecedores ou diretivas. Como resultado, você precisa adicionar seus próprios mecanismos intermediários ao sistema, receber esses dados e colocá-los em contexto.

Agora, precisamos descrever nosso próprio mecanismo de autenticação intermediária para obter dados de autenticação da solicitação e verificá-los.

Nenhuma lógica é definida aqui. Em vez disso, para dados de autorização, para fins de demonstração, o ID do usuário ID simplesmente passado aqui. Esse mecanismo é combinado no server.go com um novo método de carregamento de configuração.

Agora a descrição da diretiva faz sentido. Não processamos solicitações de usuário não autorizadas no código do middleware, pois essas solicitações serão processadas pela diretiva. Aqui está como fica.


Trabalhar com um usuário não autorizado


Trabalhar com um usuário autorizado

Ao trabalhar com diretivas de esquema, você pode até passar argumentos:

 directive @hasRole(role: Role!) on FIELD_DEFINITION enum Role { ADMIN USER } 

Carregadores de dados


Parece-me que tudo isso parece bastante interessante. Você baixa os dados quando precisar. Os clientes têm a capacidade de gerenciar dados; exatamente o que é necessário é retirado do armazenamento. Mas tudo tem um preço.

Qual o preço a pagar por essas oportunidades? Dê uma olhada nos logs de download de todos os vídeos. Ou seja, estamos falando sobre o fato de termos 8 vídeos e 5 usuários.

 query{ Videos(limit: 10){   name   user{     name   } } } 


Detalhes de Download de Vídeo

 Query: Videos : SELECT id, name, description, url, created_at, user_id FROM videos ORDER BY created_at desc limit $1 offset $2 Resolver: User : SELECT id, name, email FROM users where id = $1 Resolver: User : SELECT id, name, email FROM users where id = $1 Resolver: User : SELECT id, name, email FROM users where id = $1 Resolver: User : SELECT id, name, email FROM users where id = $1 Resolver: User : SELECT id, name, email FROM users where id = $1 Resolver: User : SELECT id, name, email FROM users where id = $1 Resolver: User : SELECT id, name, email FROM users where id = $1 Resolver: User : SELECT id, name, email FROM users where id = $1 

O que está acontecendo aqui? Por que existem 9 solicitações (uma solicitação está associada à tabela de vídeo e 8 - à tabela do usuário)? Parece horrível. Meu coração quase parou quando pensei que nossa API existente teria que ser substituída por isso ... É verdade que os carregadores de dados podem lidar completamente com esse problema.

Isso é conhecido como problema N + 1. Estamos falando do fato de que existe uma consulta para obter todos os dados e, para cada parte dos dados (N), haverá outra consulta no banco de dados.

Esse é um problema muito sério quando se trata de desempenho e recursos: embora essas solicitações sejam paralelas, elas drenam os recursos do sistema.

Para resolver esse problema, usaremos a biblioteca dataloaden do autor da biblioteca gqlgen. Essa biblioteca permite gerar o código Go. Primeiro, gere um carregador de dados para a entidade User :

 go get github.com/vektah/dataloaden dataloaden github.com/ridhamtarpara/go-graphql-demo/api.User 

Temos à disposição o arquivo userloader_gen.go , que possui métodos como Fetch , LoadAll e Prime .

Agora, para obter resultados gerais, precisamos definir o método Fetch (arquivo dataloader.go ):

 func DataloaderMiddleware(db *sql.DB, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {   userloader := UserLoader{     wait : 1 * time.Millisecond,     maxBatch: 100,     fetch: func(ids []int) ([]*api.User, []error) {       var sqlQuery string       if len(ids) == 1 {         sqlQuery = "SELECT id, name, email from users WHERE id = ?"       } else {         sqlQuery = "SELECT id, name, email from users WHERE id IN (?)"       }       sqlQuery, arguments, err := sqlx.In(sqlQuery, ids)       if err != nil {         log.Println(err)       }       sqlQuery = sqlx.Rebind(sqlx.DOLLAR, sqlQuery)       rows, err := dal.LogAndQuery(db, sqlQuery, arguments...)       defer rows.Close();       if err != nil {         log.Println(err)       }       userById := map[int]*api.User{}       for rows.Next() {         user:= api.User{}         if err := rows.Scan(&user.ID, &user.Name, &user.Email); err != nil {           errors.DebugPrintf(err)           return nil, []error{errors.InternalServerError}         }         userById[user.ID] = &user       }       users := make([]*api.User, len(ids))       for i, id := range ids {         users[i] = userById[id]         i++       }       return users, nil     },   }   ctx := context.WithValue(r.Context(), CtxKey, &userloader)   r = r.WithContext(ctx)   next.ServeHTTP(w, r) }) } 

Aqui esperamos 1 ms. antes de executar a solicitação e coletar as solicitações em pacotes de até 100 solicitações. Agora, em vez de executar uma solicitação para cada usuário individualmente, o carregador aguardará o tempo especificado antes de acessar o banco de dados. Em seguida, você precisa alterar a lógica do reconhecedor reconfigurando-a usando a solicitação para usar o carregador de dados (arquivo resolver.go ):

 func (r *videoResolver) User(ctx context.Context, obj *api.Video) (api.User, error) { user, err := ctx.Value(dataloaders.CtxKey).(*dataloaders.UserLoader).Load(obj.UserID) return *user, err } 

Veja como os logs cuidam disso em uma situação semelhante à descrita acima:

 Query: Videos : SELECT id, name, description, url, created_at, user_id FROM videos ORDER BY created_at desc limit $1 offset $2 Dataloader: User : SELECT id, name, email from users WHERE id IN ($1, $2, $3, $4, $5) 

Apenas duas consultas ao banco de dados são executadas aqui; como resultado, todos estão felizes agora. É interessante notar que apenas 5 identificadores de usuários são enviados para a solicitação, embora dados sejam solicitados para 8 vídeos. Isso sugere que o carregador de dados remova registros duplicados.


GraphQL API , . , API DOS-.

, .

Video , . GraphQL Video . . — .

, — :

 { Videos(limit: 10, offset: 0){   name   url   related(limit: 10, offset: 0){     name     url     related(limit: 10, offset: 0){       name       url       related(limit: 100, offset: 0){         name         url       }     }   } } } 

100, . (, , ) , .

gqlgen , . , ( handler.ComplexityLimit(300) ) GraphQL (300 ). , ( server.go ):

 rootHandler:= dataloaders.DataloaderMiddleware( db, handler.GraphQL(   go_graphql_demo.NewExecutableSchema(go_graphql_demo.NewRootResolvers(db)),   handler.ComplexityLimit(300) ), ) 

, , . 12. , , , ( , , , , ). resolver.go :

 func NewRootResolvers(db *sql.DB) Config { c := Config{   Resolvers: &Resolver{     db: db,   }, } //  countComplexity := func(childComplexity int, limit *int, offset *int) int {   return *limit * childComplexity } c.Complexity.Query.Videos = countComplexity c.Complexity.Video.Related = countComplexity //   c.Directives.IsAuthenticated = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {   ctxUserID := ctx.Value(UserIDCtxKey)   if ctxUserID != nil {     return next(ctx)   } else {     return nil, errors.UnauthorisedError   } } return c } 

, , .







, , related . , , , , .

Sumário


, , GitHub . . , , .

Caros leitores! GraphQL , Go?

Source: https://habr.com/ru/post/pt444346/


All Articles