La technologie GraphQL au cours des derniÚres années, aprÚs que la société Facebook l'a transférée dans la catégorie open-source, est devenue trÚs populaire. L'auteur du document, dont nous publions la traduction aujourd'hui, dit qu'il a essayé de travailler avec GraphQL dans Node.js et, d'aprÚs sa propre expérience, était convaincu que cette technologie, en raison de ses capacités et de sa simplicité remarquables, n'attire pas accidentellement autant d'attention. Récemment, alors qu'il était engagé dans un nouveau projet, il est passé de Node.js à Golang. Puis il a décidé de tester la collaboration de Golang et GraphQL.

Informations préliminaires
Vous pouvez apprendre de la dĂ©finition officielle de GraphQL qu'il s'agit d'un langage de requĂȘte pour l'API et d'un runtime pour exĂ©cuter de telles requĂȘtes sur des donnĂ©es existantes. GraphQL fournit une description complĂšte et comprĂ©hensible des donnĂ©es dans une certaine API, permet aux clients de demander exactement les informations dont ils ont besoin, et rien de plus, simplifie le dĂ©veloppement de l'API au fil du temps et offre aux dĂ©veloppeurs des outils puissants.
Il n'y a pas beaucoup de bibliothÚques GraphQL pour Golang. En particulier, j'ai essayé des bibliothÚques comme
Thunder ,
graphql ,
graphql-go et
gqlgen . Je dois noter que le meilleur de tout ce que j'ai essayé était la bibliothÚque gqlgen.
La bibliothĂšque gqlgen est toujours en version bĂȘta, au moment de la rĂ©daction de ce document, il s'agissait de la version
0.7.2 . La bibliothÚque évolue rapidement.
Ici vous pouvez découvrir les plans de son développement. Maintenant, le sponsor officiel de gqlgen est le projet
99designs , ce qui signifie que cette bibliothÚque, trÚs probablement, se développera encore plus rapidement qu'auparavant. Les principaux développeurs de cette bibliothÚque sont
vektah et
neelance , tandis que neelance, en plus, travaille sur la bibliothĂšque graphql-go.
Parlons de la bibliothÚque gqlgen en supposant que vous avez déjà des connaissances de base de GraphQL.
Fonctionnalités de Gqlgen
Dans la description de gqlgen, vous pouvez découvrir ce que nous avons devant nous est une bibliothÚque pour créer rapidement des serveurs GraphQL strictement typés dans Golang. Cette phrase me semble trÚs prometteuse, car elle signifie que lorsque je travaille avec cette bibliothÚque, je ne rencontrerai pas quelque chose comme
map[string]interface{}
, car une approche basée sur un typage strict est utilisée ici.
De plus, cette bibliothÚque utilise une approche basée sur un schéma de données. Cela signifie que les API sont décrites à l'aide du langage de
définition de schéma GraphQL. Ce langage possÚde ses propres outils de génération de code puissants qui créent automatiquement du code GraphQL. Dans ce cas, le programmeur ne peut implémenter que la logique de base des méthodes d'interface correspondantes.
Cet article est divisé en deux parties. Le premier est consacré aux méthodes de travail de base et le second aux méthodes avancées.
Les principales méthodes de travail: configuration, demandes de réception et de modification de données, abonnements
En tant qu'application expĂ©rimentale, nous utiliserons un site oĂč les utilisateurs pourront publier des vidĂ©os, ajouter des captures d'Ă©cran et des critiques, rechercher des vidĂ©os et afficher des listes d'enregistrements associĂ©s Ă d'autres enregistrements. Commençons Ă travailler sur ce projet:
mkdir -p $GOPATH/src/github.com/ridhamtarpara/go-graphql-demo/
Créez le fichier de schéma de données suivant (
schema.graphql
) dans le répertoire racine du projet:
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
Il décrit les modÚles de données de base, une mutation (
Mutation
, description de la demande de modification des donnĂ©es), qui est utilisĂ©e pour publier de nouveaux fichiers vidĂ©o sur le site, et une requĂȘte (
Query
) pour obtenir une liste de tous les fichiers vidéo. En savoir plus sur le schéma GraphQL
ici . De plus, nous avons déclaré ici l'un de nos propres types de données scalaires. Nous ne sommes pas satisfaits des 5
types de données scalaires standard (
Int
,
Float
,
String
,
Boolean
et
ID
) qui sont dans GraphQL.
Si vous devez utiliser vos propres types, vous pouvez les déclarer dans
schema.graphql
(dans notre cas, ce type est
Timestamp
) et fournir leurs définitions dans le code. Lorsque vous utilisez la bibliothÚque gqlgen, vous devez fournir des méthodes de marshaling et de démarshaling pour tous vos propres types scalaires et configurer le mappage à l'aide de
gqlgen.yml
.
Il convient de noter que dans la derniÚre version de la bibliothÚque, il y a eu un changement important. à savoir, la dépendance à l'égard des fichiers binaires compilés en a été supprimée. Par conséquent, le fichier
scripts/gqlgen.go
doit ĂȘtre ajoutĂ© au projet
scripts/gqlgen.go
contenu suivant:
AprĂšs cela, vous devez initialiser
dep
:
dep init
Il est maintenant temps de profiter des capacitĂ©s de gĂ©nĂ©ration de code de la bibliothĂšque. Ils vous permettent de crĂ©er tout le code passe-partout ennuyeux, qui, cependant, ne peut pas ĂȘtre qualifiĂ© de complĂštement inintĂ©ressant. Pour dĂ©marrer le mĂ©canisme de gĂ©nĂ©ration automatique de code, exĂ©cutez la commande suivante:
go run scripts/gqlgen.go init
à la suite de son exécution, les fichiers suivants seront créés:
gqlgen.yml
: fichier de configuration pour gérer la génération de code.
generated.go
: code généré.
models_gen.go
: tous les modÚles et types de données du schéma fourni.
resolver.go
: voici le code que le programmeur crée.
server/server.go
: point d'entrée avec http.Handler
pour démarrer le serveur GraphQL.
Jetez un Ćil au modĂšle gĂ©nĂ©rĂ© pour le type de
Video
(fichier
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"` }
Ici, vous pouvez voir que l'
ID
est une chaĂźne,
CreatedAt
est également une chaßne. Les autres modÚles associés sont configurés en conséquence. Cependant, dans les applications réelles, cela n'est pas nécessaire. Si vous utilisez n'importe quel type de données SQL, vous devez, par exemple, que le champ
ID
soit, selon la base de données utilisée, de type
int
ou
int64
.
Par exemple, j'utilise PostgreSQL dans cette application de démonstration, donc bien sûr, j'ai besoin que le champ
ID
soit de type
int
et
CreatedAt
type
time.Time
. Cela conduit au fait que nous devons définir notre propre modÚle et dire à gqlgen que nous devons utiliser notre modÚle au lieu d'en générer un nouveau. Voici le contenu du fichier
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 }
Nous indiquons Ă la bibliothĂšque qu'elle doit utiliser ces modĂšles (fichier
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
Le point de tout cela est que nous avons maintenant nos propres définitions d'
ID
et d'
Timestamp
avec des méthodes de marshaling et de démarshaling et de mappage dans le fichier
gqlgen.yml
. Maintenant que l'utilisateur fournit la chaĂźne en tant
UnmarshalID()
, la méthode
UnmarshalID()
convertit cette chaßne en un entier. Lors de l'envoi d'une réponse, la méthode
MarshalID()
convertit le nombre en chaĂźne. La mĂȘme chose se produit avec
Timestamp
ou avec tout autre type scalaire déclaré par le programmeur.
Il est maintenant temps de mettre en Ćuvre la logique d'application. Ouvrez le fichier
resolver.go
et ajoutez-y des descriptions des mutations et des requĂȘtes. Il existe dĂ©jĂ un code passe-partout gĂ©nĂ©rĂ© automatiquement que nous devons remplir de sens. Voici le code de ce fichier:
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 }
Maintenant, testons la mutation.
Mutation createVideoĂa marche! Mais pourquoi n'y a-t-il rien dans les informations
user
(objet
user
)? Lorsque vous travaillez avec GraphQL, des concepts similaires au chargement «paresseux» (paresseux) et «gourmand» (impatient) sont applicables. Ătant donnĂ© que ce systĂšme est extensible, vous devez spĂ©cifier quels champs doivent ĂȘtre remplis «avec avidité» et lesquels sont «paresseux».
J'ai suggĂ©rĂ© Ă l'Ă©quipe de l'organisation oĂč je travaille la «rĂšgle d'or» suivante qui s'applique lors de l'utilisation de gqlgen: «N'incluez pas dans le modĂšle les champs qui doivent ĂȘtre chargĂ©s uniquement s'ils sont demandĂ©s par le client.»
Dans notre cas, je dois tĂ©lĂ©charger des donnĂ©es sur les clips vidĂ©o associĂ©s (et mĂȘme des informations utilisateur) uniquement si le client demande ces champs. Mais puisque nous avons inclus ces champs dans le modĂšle, gqlgen suppose que nous fournissons ces donnĂ©es en recevant des informations sur la vidĂ©o. En consĂ©quence, nous obtenons maintenant des structures vides.
Parfois, il arrive qu'un certain type de données soit nécessaire à chaque fois, il est donc impossible de le télécharger à l'aide d'une demande distincte. Pour cela, afin d'améliorer les performances, vous pouvez utiliser quelque chose comme des jointures SQL. Une fois (cela ne s'applique toutefois pas à l'exemple considéré ici), j'ai dû télécharger ses métadonnées avec la vidéo. Ces entités étaient stockées à différents endroits. Par conséquent, si mon systÚme recevait une demande de téléchargement d'une vidéo, je devais faire une autre demande pour obtenir des métadonnées. Mais, comme je connaissais cette exigence (c'est-à -dire que je savais que le client et la vidéo et ses métadonnées sont toujours nécessaires du cÎté client), j'ai préféré utiliser la technique de chargement gourmand pour améliorer les performances.
Réécrivons le modÚle et générons à nouveau le code gqlgen. Afin de ne pas compliquer l'histoire, nous écrivons uniquement des méthodes pour le champ
user
(fichier
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"` }
Nous avons ajouté un
UserID
et supprimé la structure
User
. Régénérez maintenant le code:
go run scripts/gqlgen.go -v
Grùce à cette commande, les méthodes d'interface suivantes seront créées pour résoudre les structures non définies. De plus, vous devrez déterminer les éléments suivants dans le résolveur (fichier généré.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) }
Voici la définition (fichier
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 }
Maintenant, les résultats du test de mutation se présenteront comme indiqué ci-dessous.
Mutation createVideoCe que nous venons de discuter, ce sont les principes fondamentaux de GraphQL, aprĂšs avoir maĂźtrisĂ© lequel, vous pouvez dĂ©jĂ Ă©crire quelque chose de vous-mĂȘme. Cependant, avant de vous lancer dans des expĂ©riences avec GraphQL et Golang, il sera utile de parler des abonnements, qui sont directement liĂ©s Ă ce que nous faisons ici.
â Abonnements
GraphQL offre la possibilité de s'abonner aux modifications de données qui se produisent en temps réel. La bibliothÚque gqlgen permet, en temps réel, à l'aide de sockets web, de travailler avec des événements d'abonnement.
L'abonnement doit ĂȘtre dĂ©crit dans le fichier
schema.graphql
. Voici la description de l'abonnement à l'événement de publication vidéo:
type Subscription { videoPublished: Video! }
Maintenant, exécutez à nouveau la génération automatique de code:
go run scripts/gqlgen.go -v
Comme dĂ©jĂ mentionnĂ©, lors de la crĂ©ation automatique de code dans le fichier gĂ©nĂ©rĂ©.go, une interface est créée qui doit ĂȘtre implĂ©mentĂ©e dans le module de reconnaissance. Dans notre cas, cela ressemble Ă ceci (fichier
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) {
Maintenant, lorsque vous créez une nouvelle vidéo, vous devez déclencher un événement. Dans notre exemple, cela se fait dans la ligne
for _, observer := range videoPublishedChannel
.
Il est maintenant temps de vérifier votre abonnement.
VĂ©rifier l'abonnementGraphQL, bien sĂ»r, possĂšde certaines capacitĂ©s prĂ©cieuses, mais comme on dit, tout ce qui brille n'est pas or. Ă savoir, nous parlons du fait que quelqu'un qui utilise GraphQL doit prendre soin de l'autorisation, de la complexitĂ© des demandes, de la mise en cache, du problĂšme des demandes N + 1, de la limitation de la vitesse d'exĂ©cution des requĂȘtes et d'autres choses. Sinon, un systĂšme dĂ©veloppĂ© Ă l'aide de GraphQL peut faire face Ă une baisse sĂ©rieuse des performances.
Techniques avancĂ©es: authentification, chargeurs de donnĂ©es, complexitĂ© des requĂȘtes
Chaque fois que je lis des manuels comme celui-ci, j'ai l'impression qu'aprÚs les avoir maßtrisés, j'apprends tout ce que je dois savoir sur une certaine technologie et j'ai la capacité de résoudre des problÚmes de toute complexité.
Mais lorsque je commence Ă travailler sur mes propres projets, je me retrouve gĂ©nĂ©ralement dans des situations imprĂ©vues qui ressemblent Ă des erreurs de serveur ou Ă des demandes en cours depuis des siĂšcles, ou Ă d'autres situations de blocage. Par consĂ©quent, pour ce faire, je dois mieux me plonger dans ce qui, tout rĂ©cemment, semblait parfaitement comprĂ©hensible. Dans ce mĂȘme manuel, j'espĂšre que cela pourra ĂȘtre Ă©vitĂ©. C'est pourquoi dans cette section, nous examinerons quelques techniques avancĂ©es pour travailler avec GraphQL.
â Authentification
Lorsque vous travaillez avec l'API REST, nous avons un systĂšme d'authentification et des outils d'autorisation standard lorsque vous travaillez avec un certain point de terminaison. Mais lorsque vous utilisez GraphQL, un seul point de terminaison est utilisĂ©, par consĂ©quent, les tĂąches d'authentification peuvent ĂȘtre rĂ©solues Ă l'aide de directives de schĂ©ma. Modifiez le fichier
schema.graphql
comme suit:
type Mutation { createVideo(input: NewVideo!): Video! @isAuthenticated } directive @isAuthenticated on FIELD_DEFINITION
Nous avons créé la directive
isAuthenticated
et l'
isAuthenticated
appliquée à l'abonnement
createVideo
. AprÚs la prochaine session de génération automatique de code, vous devez définir une définition pour cette directive. Maintenant, les directives sont implémentées sous forme de méthodes de structures, et non sous forme d'interfaces, nous devons donc les décrire. J'ai édité le code généré automatiquement situé dans le fichier
server.go
et créé une méthode qui renvoie la configuration GraphQL pour le fichier
server.go
. Voici le fichier
resolver.go
:
func NewRootResolvers(db *sql.DB) Config { c := Config{ Resolvers: &Resolver{ db: db, }, }
Voici le fichier
server.go
:
rootHandler:= dataloaders.DataloaderMiddleware( db, handler.GraphQL( go_graphql_demo.NewExecutableSchema(go_graphql_demo.NewRootResolvers(db) ) ) http.Handle("/query", auth.AuthMiddleware(rootHandler))
Nous lisons l'
ID
utilisateur dans le contexte. Ne trouvez-vous pas cela Ă©trange? Comment ce sens est-il entrĂ© dans son contexte et pourquoi est-il mĂȘme apparu dans son contexte? Le fait est que gqlgen fournit des contextes de requĂȘte uniquement au niveau de l'implĂ©mentation, nous n'avons donc aucun moyen de lire les donnĂ©es de requĂȘte HTTP, telles que les en-tĂȘtes ou les cookies, dans les programmes de reconnaissance ou les directives. Par consĂ©quent, vous devez ajouter vos propres mĂ©canismes intermĂ©diaires au systĂšme, recevoir ces donnĂ©es et les mettre en contexte.
Nous devons maintenant décrire notre propre mécanisme d'authentification intermédiaire pour obtenir les données d'authentification de la demande et les vérifier.
Aucune logique n'est définie ici. Au lieu de cela, pour les données d'autorisation, à des fins de démonstration, l'
ID
utilisateur
ID
simplement transmis ici. Ce mécanisme est ensuite combiné dans
server.go
avec une nouvelle méthode de chargement de configuration.
Maintenant, la description de la directive a du sens. Nous ne traitons pas les demandes d'utilisateurs non autorisés dans le code du middleware, car ces demandes seront traitées par la directive. Voici à quoi ça ressemble.
Travailler avec un utilisateur non autorisĂ©Travailler avec un utilisateur autorisĂ©Lorsque vous travaillez avec des directives de schĂ©ma, vous pouvez mĂȘme passer des arguments:
directive @hasRole(role: Role!) on FIELD_DEFINITION enum Role { ADMIN USER }
â Chargeurs de donnĂ©es
Il me semble que tout cela semble assez intéressant. Vous téléchargez des données lorsque vous en avez besoin. Les clients ont la capacité de gérer les données; exactement ce qui est nécessaire provient du stockage. Mais tout a un prix.
Quel est le prix Ă payer pour ces opportunitĂ©s? Jetez un Ćil aux journaux de tĂ©lĂ©chargement de toutes les vidĂ©os. A savoir, nous parlons du fait que nous avons 8 vidĂ©os et 5 utilisateurs.
query{ Videos(limit: 10){ name user{ name } } }
Détails du téléchargement de la vidéo 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
Que se passe-t-il ici? Pourquoi y a-t-il 9 demandes (1 demande est associĂ©e Ă la table vidĂ©o et 8 - Ă la table utilisateur)? Ăa a l'air horrible. Mon cĆur s'est presque arrĂȘtĂ© quand j'ai pensĂ© que notre API existante devrait ĂȘtre remplacĂ©e par ceci ... Vrai, les chargeurs de donnĂ©es peuvent complĂštement faire face Ă ce problĂšme.
Ceci est connu comme le problĂšme N + 1. Nous parlons du fait qu'il y a une requĂȘte pour obtenir toutes les donnĂ©es et pour chaque Ă©lĂ©ment de donnĂ©es (N) il y aura une autre requĂȘte dans la base de donnĂ©es.
Il s'agit d'un problĂšme trĂšs grave en termes de performances et de ressources: bien que ces demandes soient parallĂšles, elles drainent les ressources systĂšme.
Pour résoudre ce problÚme, nous utiliserons la bibliothÚque
dataloaden de l'auteur de la bibliothÚque gqlgen. Cette bibliothÚque vous permet de générer du code Go. Tout d'abord, générez un chargeur de données pour l'entité
User
:
go get github.com/vektah/dataloaden dataloaden github.com/ridhamtarpara/go-graphql-demo/api.User
Nous avons Ă notre disposition le fichier
userloader_gen.go
, qui a des méthodes comme
Fetch
,
LoadAll
et
Prime
.
Maintenant, pour obtenir des résultats généraux, nous devons définir la méthode
Fetch
(fichier
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) }) }
Ici, nous attendons 1 ms. avant d'exécuter la demande et de collecter les demandes dans des packages contenant jusqu'à 100 demandes. Maintenant, au lieu d'exécuter une demande pour chaque utilisateur individuellement, le chargeur attendra le temps spécifié avant d'accéder à la base de données. Ensuite, vous devez modifier la logique de reconnaissance en la reconfigurant à l'aide de la demande d'utilisation du chargeur de données (fichier
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 }
Voici comment les journaux s'en occupent dans une situation similaire à celle décrite ci-dessus:
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)
Seules deux requĂȘtes de base de donnĂ©es sont exĂ©cutĂ©es ici, par consĂ©quent, tout le monde est maintenant satisfait. Il est intĂ©ressant de noter que seuls 5 identifiants d'utilisateurs sont envoyĂ©s Ă la demande, bien que des donnĂ©es soient demandĂ©es pour 8 vidĂ©os. Cela suggĂšre que le chargeur de donnĂ©es supprime les enregistrements en double.
â
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, }, }
, , .
, ,
related
. , , , , .
Résumé
, ,
GitHub . . , , .
Chers lecteurs! GraphQL , Go?
