GraphQL y Golang

La tecnología GraphQL en los últimos años, después de que la compañía Facebook la transfirió a la categoría de código abierto, se ha vuelto muy popular. El autor del material, cuya traducción publicamos hoy, dice que trató de trabajar con GraphQL en Node.js y, por su propia experiencia, estaba convencido de que esta tecnología, gracias a sus notables capacidades y simplicidad, no atrae tanta atención accidentalmente. Recientemente, mientras participaba en un nuevo proyecto, cambió de Node.js a Golang. Luego decidió probar la colaboración de Golang y GraphQL.



Información preliminar


Puede aprender de la definición oficial de GraphQL que este es un lenguaje de consulta para la API y un tiempo de ejecución para ejecutar tales consultas en los datos existentes. GraphQL proporciona una descripción completa y comprensible de los datos en una determinada API, permite a los clientes solicitar exactamente la información que necesitan, y nada más, simplifica el desarrollo de la API con el tiempo y brinda a los desarrolladores herramientas poderosas.

No hay muchas bibliotecas GraphQL para Golang. En particular, probé bibliotecas como Thunder , graphql , graphql-go y gqlgen . Debo señalar que lo mejor de todo lo que probé fue la biblioteca gqlgen.

La biblioteca gqlgen todavía está en beta, al momento de escribir este material era la versión 0.7.2 . La biblioteca está evolucionando rápidamente. Aquí puede conocer los planes para su desarrollo. Ahora el patrocinador oficial de gqlgen es el proyecto 99designs , lo que significa que esta biblioteca, posiblemente, se desarrollará aún más rápido que antes. Los principales desarrolladores de esta biblioteca son vektah y neelance , mientras que neelance, además, funciona en la biblioteca graphql-go.

Hablemos de la biblioteca gqlgen con el supuesto de que ya tiene conocimientos básicos de GraphQL.

Características de Gqlgen


En la descripción de gqlgen, puede encontrar lo que tenemos ante nosotros es una biblioteca para crear rápidamente servidores GraphQL estrictamente tipados en Golang. Esta frase me parece muy prometedora, ya que significa que cuando trabaje con esta biblioteca no me encontraré con algo como la map[string]interface{} , ya que aquí se utiliza un enfoque basado en una escritura estricta.

Además, esta biblioteca utiliza un enfoque basado en un esquema de datos. Esto significa que las API se describen utilizando el lenguaje de definición de esquema GraphQL. Este lenguaje tiene sus propias potentes herramientas de generación de código que crean automáticamente código GraphQL. En este caso, el programador solo puede implementar la lógica básica de los métodos de interfaz correspondientes.

Este artículo está dividido en dos partes. El primero está dedicado a los métodos básicos de trabajo y el segundo a los avanzados.

Los principales métodos de trabajo: configuración, solicitudes para recibir y cambiar datos, suscripciones


Nosotros, como aplicación experimental, utilizaremos un sitio donde los usuarios pueden publicar videos, agregar capturas de pantalla y revisiones, buscar videos y ver listas de registros asociados con otros registros. Comencemos a trabajar en este proyecto:

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

Cree el siguiente archivo de esquema de datos ( schema.graphql ) en el directorio raíz del proyecto:

 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 

Aquí se describen los modelos de datos básicos, una mutación ( Mutation , descripción de la solicitud de cambio de datos), que se utiliza para publicar nuevos archivos de video en el sitio, y una consulta ( Query ) para obtener una lista de todos los archivos de video. Lea más sobre el esquema GraphQL aquí . Además, aquí declaramos uno de nuestros propios tipos de datos escalares. No estamos satisfechos con los 5 tipos de datos escalares estándar ( Int , Float , String , Boolean e ID ) que se encuentran en GraphQL.

Si necesita usar sus propios tipos, puede declararlos en schema.graphql (en nuestro caso, este tipo es Timestamp ) y proporcionar sus definiciones en el código. Al usar la biblioteca gqlgen, debe proporcionar métodos para ordenar y desarmar para todos sus propios tipos escalares y configurar la asignación usando gqlgen.yml .

Cabe señalar que en la última versión de la biblioteca hubo un cambio importante. A saber, se eliminó la dependencia de los archivos binarios compilados. Por lo tanto, el archivo scripts/gqlgen.go debe agregarse al proyecto scripts/gqlgen.go siguiente contenido:

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

Después de eso, debe inicializar dep :

 dep init 

Ahora es el momento de aprovechar las capacidades de generación de código de la biblioteca. Le permiten crear todo el código aburrido repetitivo, que, sin embargo, no se puede llamar completamente desinteresado. Para iniciar el mecanismo de generación automática de código, ejecute el siguiente comando:

 go run scripts/gqlgen.go init 

Como resultado de su ejecución, se crearán los siguientes archivos:

  • gqlgen.yml : archivo de configuración para gestionar la generación de código.
  • generated.go : código generado.
  • models_gen.go : todos los modelos y tipos de datos del esquema proporcionado.
  • resolver.go : aquí estará el código que crea el programador.
  • server/server.go : punto de entrada con http.Handler para iniciar el servidor GraphQL.

Observe el modelo generado para el tipo de Video (archivo 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"` } 

Aquí puede ver que la ID es una cadena, CreatedAt también es una cadena. Otros modelos relacionados se configuran en consecuencia. Sin embargo, en aplicaciones reales esto no es necesario. Si está utilizando cualquier tipo de datos SQL, entonces necesita, por ejemplo, que el campo ID sea, según la base de datos utilizada, un tipo int o int64 .

Por ejemplo, uso PostgreSQL en esta aplicación de demostración, por lo que, por supuesto, necesito que el campo ID sea ​​de tipo int y CreatedAt tipo time.Time . Esto lleva al hecho de que necesitamos definir nuestro propio modelo y decirle a gqlgen que necesitamos usar nuestro modelo en lugar de generar uno nuevo. Aquí está el contenido del archivo 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 } 

Le decimos a la biblioteca que debe usar estos modelos (archivo 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 

El punto de todo esto es que ahora tenemos nuestras propias definiciones para ID y Timestamp con métodos para calcular y desglosar y asignarlos en el archivo gqlgen.yml . Ahora que el usuario proporciona la cadena como ID , el método UnmarshalID() convierte esa cadena en un entero. Al enviar una respuesta, el método MarshalID() convierte el número en una cadena. Lo mismo sucede con Timestamp o con cualquier otro tipo escalar declarado por el programador.

Ahora es el momento de implementar la lógica de la aplicación. Abra el archivo resolver.go y agregue descripciones de mutaciones y consultas en él. Ya existe un código repetitivo generado automáticamente que debemos llenar de significado. Aquí está el código para este archivo:

 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 } 

Ahora probemos la mutación.

Mutación createVideo

Funciona! Pero, ¿por qué no hay nada en la información del usuario (objeto de user )? Cuando se trabaja con GraphQL, se aplican conceptos similares a la carga "perezosa" (perezosa) y "codiciosa" (ansiosa). Dado que este sistema es extensible, debe especificar qué campos deben rellenarse "con avidez" y cuáles son "perezosos".

Le sugerí al equipo de la organización donde trabajo la siguiente "regla de oro" que se aplica al trabajar con gqlgen: "No incluya en el modelo los campos que deben cargarse solo si el cliente los solicita".

En nuestro caso, necesito descargar datos sobre videoclips relacionados (e incluso información del usuario) solo si el cliente solicita estos campos. Pero como incluimos estos campos en el modelo, gqlgen supone que proporcionamos estos datos al recibir información sobre el video. Como resultado, ahora tenemos estructuras vacías.

A veces sucede que se necesita un cierto tipo de datos cada vez, por lo que no es práctico descargarlo usando una solicitud por separado. Para esto, para mejorar el rendimiento, puede usar algo como las uniones SQL. Una vez (esto, sin embargo, no se aplica al ejemplo considerado aquí), necesitaba cargar sus metadatos junto con el video. Estas entidades fueron almacenadas en diferentes lugares. Como resultado, si mi sistema recibió una solicitud para descargar un video, tenía que hacer otra solicitud para obtener metadatos. Pero, dado que conocía este requisito (es decir, sabía que el cliente y el video y sus metadatos siempre son necesarios en el lado del cliente), preferí usar la técnica de carga codiciosa para mejorar el rendimiento.

Reescribamos el modelo y generemos el código gqlgen nuevamente. Para no complicar la historia, solo escribimos métodos para el campo de user (archivo 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"` } 

UserID una UserID User y eliminamos la estructura de User . Ahora regenere el código:

 go run scripts/gqlgen.go -v 

Gracias a este comando, se crearán los siguientes métodos de interfaz para resolver estructuras indefinidas. Además, deberá determinar lo siguiente en el solucionador (archivo generated.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) } 

Aquí está la definición (archivo 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 } 

Ahora, los resultados de la prueba de mutación se verán como se muestra a continuación.


Mutación createVideo

Lo que acabamos de discutir son los fundamentos de GraphQL, habiendo dominado cuáles, ya puedes escribir algo por tu cuenta. Sin embargo, antes de sumergirse en experimentos con GraphQL y Golang, será útil hablar sobre las suscripciones, que están directamente relacionadas con lo que estamos haciendo aquí.

▍ Suscripciones


GraphQL proporciona la capacidad de suscribirse a los cambios de datos que ocurren en tiempo real. La biblioteca gqlgen permite, en tiempo real, usar sockets web, para trabajar con eventos de suscripción.

La suscripción debe describirse en el archivo schema.graphql . Así es como se ve la descripción para suscribirse a un evento de publicación de video:

 type Subscription {   videoPublished: Video! } 

Ahora, ejecute la generación automática de código nuevamente:

 go run scripts/gqlgen.go -v 

Como ya se mencionó, durante la creación automática de código en el archivo generated.go , se crea una interfaz que debe implementarse en el reconocedor. En nuestro caso, se ve así (archivo 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 } 

Ahora, al crear un nuevo video, debe activar un evento. En nuestro ejemplo, esto se hace en la línea for _, observer := range videoPublishedChannel .

Ahora es el momento de verificar su suscripción.


Verificar suscripción

GraphQL, por supuesto, tiene ciertas capacidades valiosas, pero como dicen, no todo lo que brilla es oro. Es decir, estamos hablando del hecho de que alguien que usa GraphQL debe ocuparse de la autorización, la complejidad de las solicitudes, el almacenamiento en caché, el problema de las solicitudes N + 1, la limitación de la velocidad de ejecución de consultas y algunas otras cosas. De lo contrario, un sistema desarrollado con GraphQL puede enfrentar una seria caída en el rendimiento.

Técnicas avanzadas: autenticación, cargadores de datos, complejidad de consultas


Cada vez que leo manuales como este, tengo la sensación de que, después de dominarlos, aprendo todo lo que necesito saber sobre cierta tecnología y tengo la capacidad de resolver problemas de cualquier complejidad.

Pero cuando comienzo a trabajar en mis propios proyectos, generalmente me encuentro con situaciones imprevistas que parecen errores del servidor o solicitudes que se han estado ejecutando durante años, o como algunas otras situaciones de punto muerto. Como resultado, para hacer esto, tengo que profundizar en lo que recientemente parecía perfectamente entendible. En este mismo manual, espero que esto se pueda evitar. Es por eso que en esta sección veremos algunas técnicas avanzadas para trabajar con GraphQL.

▍ Autenticación


Cuando trabajamos con la API REST, tenemos un sistema de autenticación y herramientas de autorización estándar cuando trabajamos con un determinado punto final. Pero cuando se usa GraphQL, solo se usa un punto final, por lo tanto, las tareas de autenticación se pueden resolver usando directivas de esquema. Edite el archivo schema.graphql la siguiente manera:

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

Creamos la directiva isAuthenticated y la aplicamos a la suscripción createVideo . Después de la próxima sesión de generación automática de código, debe definir una definición para esta directiva. Ahora las directivas se implementan en forma de métodos de estructuras, y no en forma de interfaces, por lo que debemos describirlas. Edité el código generado automáticamente ubicado en el archivo server.go y creé un método que devuelve la configuración GraphQL para el archivo server.go . Aquí está el archivo 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 } 

Aquí está el archivo server.go :

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

Leemos la ID usuario del contexto. ¿No te parece extraño? ¿Cómo llegó este significado al contexto y por qué incluso apareció en el contexto? El hecho es que gqlgen proporciona contextos de solicitud solo a nivel de implementación, por lo que no tenemos forma de leer ningún dato de solicitud HTTP, como encabezados o cookies, en reconocedores o directivas. Como resultado, debe agregar sus propios mecanismos intermedios al sistema, recibir estos datos y ponerlos en contexto.

Ahora necesitamos describir nuestro propio mecanismo de autenticación intermedio para obtener datos de autenticación de la solicitud y verificarlo.

No se define lógica aquí. En cambio, para los datos de autorización, con fines de demostración, la ID usuario simplemente se pasa aquí. Este mecanismo se combina en server.go con un nuevo método de carga de configuración.

Ahora la descripción de la directiva tiene sentido. No procesamos solicitudes de usuarios no autorizados en el código de middleware, ya que dichas solicitudes serán procesadas por la directiva. Así es como se ve.


Trabajar con un usuario no autorizado.


Trabajar con un usuario autorizado

Al trabajar con directivas de esquema, incluso puede pasar argumentos:

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

▍ Cargadores de datos


Me parece que todo esto parece bastante interesante. Descarga datos cuando los necesita. Los clientes tienen la capacidad de administrar datos; exactamente lo que se necesita se toma del almacenamiento. Pero todo tiene un precio.

¿Cuál es el precio a pagar por estas oportunidades? Echa un vistazo a los registros de descarga de todos los videos. Es decir, estamos hablando del hecho de que tenemos 8 videos y 5 usuarios.

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


Detalles de descarga de video

 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 

¿Qué está pasando aquí? ¿Por qué hay 9 solicitudes (1 solicitud está asociada con la tabla de video y 8 - con la tabla de usuario)? Se ve horrible Mi corazón casi se detuvo cuando pensé que nuestra API existente tendría que ser reemplazada por esto ... Es cierto, los cargadores de datos pueden hacer frente por completo a este problema.

Esto se conoce como el problema N + 1. Estamos hablando del hecho de que hay una consulta para obtener todos los datos y para cada pieza de datos (N) habrá otra consulta en la base de datos.

Este es un problema muy serio cuando se trata de rendimiento y recursos: aunque estas solicitudes son paralelas, agotan los recursos del sistema.

Para resolver este problema, utilizaremos la biblioteca de carga de datos del autor de la biblioteca gqlgen. Esta biblioteca le permite generar código Go. Primero, genere un cargador de datos para la entidad User :

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

Tenemos a nuestra disposición el archivo userloader_gen.go , que tiene métodos como Fetch , LoadAll y Prime .

Ahora, para obtener resultados generales, necesitamos definir el método Fetch (archivo 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) }) } 

Aquí esperamos 1 ms. antes de ejecutar la solicitud y recopilar las solicitudes en paquetes de hasta 100 solicitudes. Ahora, en lugar de ejecutar una solicitud para cada usuario individualmente, el cargador esperará el tiempo especificado antes de acceder a la base de datos. A continuación, debe cambiar la lógica del reconocedor reconfigurándola utilizando la solicitud para usar el cargador de datos (archivo 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 } 

Así es como se ven los registros después de eso en una situación similar a la descrita anteriormente:

 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) 

Aquí solo se ejecutan dos consultas a la base de datos, como resultado, todos están contentos. Es interesante observar que solo se envían 5 identificadores de usuario a la solicitud, aunque se solicitan datos para 8 videos. Esto sugiere que el cargador de datos elimina 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 . , , , , .

Resumen


, , GitHub . . , , .

Estimados lectores! GraphQL , Go?

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


All Articles