Die GraphQL-Technologie ist in den letzten Jahren, nachdem das Unternehmen Facebook sie in die Kategorie Open Source übertragen hat, sehr beliebt geworden. Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, sagt, er habe versucht, mit GraphQL in Node.js zu arbeiten, und war aus eigener Erfahrung davon überzeugt, dass diese Technologie aufgrund ihrer bemerkenswerten Fähigkeiten und Einfachheit nicht versehentlich so viel Aufmerksamkeit erregt. Kürzlich wechselte er bei einem neuen Projekt von Node.js zu Golang. Dann beschloss er, die Zusammenarbeit von Golang und GraphQL zu testen.

Vorläufige Informationen
Sie können der offiziellen GraphQL-Definition entnehmen, dass dies eine Abfragesprache für die API und eine Laufzeit für die Ausführung solcher Abfragen für vorhandene Daten ist. GraphQL bietet eine vollständige und verständliche Beschreibung der Daten in einer bestimmten API, ermöglicht es Kunden, genau die Informationen anzufordern, die sie benötigen, und nichts weiter vereinfacht die Entwicklung der API im Laufe der Zeit und bietet Entwicklern leistungsstarke Tools.
Es gibt nicht viele GraphQL-Bibliotheken für Golang. Insbesondere habe ich Bibliotheken wie 
Thunder , 
graphql , 
graphql-go und 
gqlgen ausprobiert . Ich sollte beachten, dass das Beste von allem, was ich versucht habe, die gqlgen-Bibliothek war.
Die gqlgen-Bibliothek befindet sich noch in der Beta- 
Phase . 
Zum Zeitpunkt des Schreibens dieses Materials war es Version 
0.7.2 . Die Bibliothek entwickelt sich rasant weiter. 
Hier erfahren Sie mehr über Pläne für die Entwicklung. Jetzt ist der offizielle Sponsor von gqlgen das 
99designs- Projekt, was bedeutet, dass sich diese Bibliothek möglicherweise noch schneller als zuvor entwickeln wird. Die Hauptentwickler dieser Bibliothek sind 
vektah und 
neelance , während neelance zusätzlich an der graphql-go-Bibliothek arbeitet.
Lassen Sie uns über die gqlgen-Bibliothek sprechen, basierend auf der Annahme, dass Sie bereits Grundkenntnisse in GraphQL haben.
Gqlgen Funktionen
In der Beschreibung von gqlgen finden Sie heraus, was wir vor uns haben, eine Bibliothek zum schnellen Erstellen streng typisierter GraphQL-Server in Golang. Dieser Satz erscheint mir sehr vielversprechend, da er bedeutet, dass ich bei der Arbeit mit dieser Bibliothek nicht auf so etwas wie 
map[string]interface{} , da hier ein Ansatz verwendet wird, der auf strenger Typisierung basiert.
Darüber hinaus verwendet diese Bibliothek einen Ansatz, der auf einem Datenschema basiert. Dies bedeutet, dass die APIs mithilfe der GraphQL 
Schema Definition Language beschrieben werden . Diese Sprache verfügt über eigene leistungsstarke Tools zur Codegenerierung, die automatisch GraphQL-Code erstellen. In diesem Fall kann der Programmierer nur die Grundlogik der entsprechenden Schnittstellenmethoden implementieren.
Dieser Artikel ist in zwei Teile gegliedert. Die erste widmet sich den grundlegenden Arbeitsmethoden und die zweite den fortgeschrittenen.
Die wichtigsten Arbeitsmethoden: Einrichten, Anforderungen zum Empfangen und Ändern von Daten, Abonnements
Als experimentelle Anwendung verwenden wir eine Website, auf der Benutzer Videos veröffentlichen, Screenshots und Bewertungen hinzufügen, nach Videos suchen und Listen mit Datensätzen anzeigen können, die mit anderen Datensätzen verknüpft sind. Beginnen wir mit der Arbeit an diesem Projekt:
 mkdir -p $GOPATH/src/github.com/ridhamtarpara/go-graphql-demo/ 
Erstellen Sie die folgende Datenschemadatei ( 
schema.graphql ) im Stammverzeichnis des Projekts:
 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 
Hier werden die grundlegenden Datenmodelle beschrieben, eine Mutation ( 
Mutation , Beschreibung der Anforderung zur Datenänderung), mit der neue Videodateien auf der Site veröffentlicht werden, und eine Abfrage ( 
Query ), um eine Liste aller Videodateien abzurufen. Lesen Sie hier mehr über das GraphQL-Schema. Außerdem haben wir hier einen unserer eigenen skalaren Datentypen deklariert. Wir sind mit den 5 standardmäßigen skalaren Datentypen ( 
Int , 
Float , 
String , 
Boolean und 
ID ) in GraphQL nicht zufrieden.
Wenn Sie Ihre eigenen Typen verwenden müssen, können Sie diese in 
schema.graphql (in unserem Fall ist dieser Typ 
Timestamp ) und ihre Definitionen im Code 
schema.graphql . Wenn Sie die gqlgen-Bibliothek verwenden, müssen Sie Methoden zum Marshalling und Unmarshaling für alle Ihre eigenen Skalartypen bereitstellen und die Zuordnung mit 
gqlgen.yml konfigurieren.
Es ist zu beachten, dass es in der neuesten Version der Bibliothek eine wichtige Änderung gab. Die Abhängigkeit von kompilierten Binärdateien wurde nämlich entfernt. Daher sollte die Datei 
scripts/gqlgen.go folgenden Inhalt zum Projekt hinzugefügt werden:
 
Danach müssen Sie dep initialisieren:
 dep init 
Jetzt ist es Zeit, die Codegenerierungsfunktionen der Bibliothek zu nutzen. Mit ihnen können Sie den gesamten langweiligen Boilerplate-Code erstellen, der jedoch nicht als völlig uninteressant bezeichnet werden kann. Führen Sie den folgenden Befehl aus, um den automatischen Codegenerierungsmechanismus zu starten:
 go run scripts/gqlgen.go init 
Als Ergebnis seiner Ausführung werden die folgenden Dateien erstellt:
- gqlgen.yml: Konfigurationsdatei zum Verwalten der Codegenerierung.
 
- generated.go: generierter Code.
 
- models_gen.go: Alle Modelle und Datentypen des bereitgestellten Schemas.
 
- resolver.go: Hier ist der Code, den der Programmierer erstellt.
 
- server/server.go: Einstiegspunkt mit- http.Handlerzum Starten des GraphQL-Servers.
 
Sehen Sie sich das generierte Modell für den 
Video (Datei 
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"` } 
Hier können Sie sehen, dass die 
ID eine Zeichenfolge ist, 
CreatedAt ist auch eine Zeichenfolge. Andere verwandte Modelle sind entsprechend konfiguriert. In realen Anwendungen ist dies jedoch nicht erforderlich. Wenn Sie einen beliebigen Typ von SQL-Daten verwenden, benötigen Sie beispielsweise, dass das 
ID Feld je nach verwendeter Datenbank ein 
int64 oder 
int64 .
Zum Beispiel verwende ich PostgreSQL in dieser Demo-Anwendung, daher muss das 
ID Feld vom Typ 
int und 
CreatedAt Typ 
time.Time . Dies führt dazu, dass wir unser eigenes Modell definieren und gqlgen mitteilen müssen, dass wir unser Modell verwenden müssen, anstatt ein neues zu generieren. Hier ist der Inhalt der Datei 
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 }  
Wir teilen der Bibliothek mit, dass sie diese Modelle verwenden soll (Datei 
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 
Der Sinn all dessen ist, dass wir jetzt unsere eigenen Definitionen für 
ID und 
Timestamp mit Methoden zum Marshalling und Unmarshaling sowie zur Zuordnung in der Datei 
gqlgen.yml . 
UnmarshalID() der Benutzer die Zeichenfolge als 
ID , 
UnmarshalID() die 
UnmarshalID() -Methode diese Zeichenfolge in eine Ganzzahl. Beim Senden einer Antwort konvertiert die 
MarshalID() -Methode die Zahl in eine Zeichenfolge. Das gleiche passiert mit 
Timestamp oder einem anderen vom Programmierer deklarierten Skalartyp.
Jetzt ist es Zeit, die Anwendungslogik zu implementieren. Öffnen Sie die Datei 
resolver.go und fügen Sie Beschreibungen von Mutationen und Abfragen hinzu. Es gibt bereits einen automatisch generierten Boilerplate-Code, den wir mit Bedeutung füllen müssen. Hier ist der Code für diese Datei:
 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 } 
Testen wir nun die Mutation.
Mutation createVideoEs funktioniert! Aber warum enthält die Benutzerinformation ( 
user ) nichts? Bei der Arbeit mit GraphQL gelten Konzepte, die dem "faulen" (faulen) und "gierigen" (eifrigen) Laden ähneln. Da dieses System erweiterbar ist, müssen Sie angeben, welche Felder "gierig" und welche "faul" ausgefüllt werden müssen.
Ich habe dem Team in der Organisation, in der ich arbeite, die folgende „goldene Regel“ vorgeschlagen, die für die Arbeit mit gqlgen gilt: „Nicht in die Modellfelder aufnehmen, die nur geladen werden müssen, wenn sie vom Kunden angefordert werden.“
In unserem Fall muss ich nur dann Daten zu verwandten Videoclips (und sogar Benutzerinformationen) herunterladen, wenn der Client diese Felder anfordert. Da wir diese Felder jedoch in das Modell aufgenommen haben, geht gqlgen davon aus, dass wir diese Daten bereitstellen, indem wir Informationen über das Video erhalten. Als Ergebnis erhalten wir jetzt leere Strukturen.
Manchmal kommt es vor, dass jedes Mal ein bestimmter Datentyp benötigt wird. Daher ist es unpraktisch, ihn über eine separate Anforderung herunterzuladen. Um die Leistung zu verbessern, können Sie dazu beispielsweise SQL-Joins verwenden. Einmal (dies gilt jedoch nicht für das hier betrachtete Beispiel) musste ich seine Metadaten zusammen mit dem Video hochladen. Diese Entitäten wurden an verschiedenen Orten gespeichert. Wenn mein System eine Anfrage zum Herunterladen eines Videos erhielt, musste ich eine weitere Anfrage stellen, um Metadaten abzurufen. Da ich jedoch über diese Anforderung Bescheid wusste (dh wusste, dass Client und Video sowie deren Metadaten immer auf der Clientseite benötigt werden), zog ich es vor, die Technik des gierigen Ladens zu verwenden, um die Leistung zu verbessern.
Lassen Sie uns das Modell neu schreiben und den gqlgen-Code erneut generieren. Um die Geschichte nicht zu komplizieren, schreiben wir nur Methoden für das 
user (Datei 
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"` } 
Wir haben eine 
User UserID hinzugefügt und die 
User entfernt. Generieren Sie nun den Code neu:
 go run scripts/gqlgen.go -v 
Dank dieses Befehls werden die folgenden Schnittstellenmethoden erstellt, um undefinierte Strukturen aufzulösen. Darüber hinaus müssen Sie im Resolver ( 
generated.go Datei) Folgendes festlegen:
 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) } 
Hier ist die Definition ( 
resolver.go Datei):
 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 } 
Die Mutationstestergebnisse sehen nun wie unten gezeigt aus.
Mutation createVideoWas wir gerade besprochen haben, sind die Grundlagen von GraphQL, nachdem Sie diese beherrschen, können Sie bereits etwas Eigenes schreiben. Bevor Sie jedoch in Experimente mit GraphQL und Golang eintauchen, sollten Sie über Abonnements sprechen, die in direktem Zusammenhang mit dem stehen, was wir hier tun.
▍ Abonnements
GraphQL bietet die Möglichkeit, Datenänderungen zu abonnieren, die in Echtzeit auftreten. Mit der gqlgen-Bibliothek können Sie mithilfe von Web-Sockets in Echtzeit mit Abonnementereignissen arbeiten.
Das Abonnement muss in der Datei 
schema.graphql werden. Hier ist die Beschreibung des Abonnements für das Video-Publishing-Ereignis:
 type Subscription {   videoPublished: Video! } 
Führen Sie nun die automatische Codegenerierung erneut aus:
 go run scripts/gqlgen.go -v 
Wie bereits erwähnt, wird während der automatischen Erstellung von Code in der Datei 
generated.go eine Schnittstelle erstellt, die im Erkenner implementiert werden muss. In unserem Fall sieht es so aus ( 
resolver.go Datei):
 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) {  
Wenn Sie jetzt ein neues Video erstellen, müssen Sie ein Ereignis auslösen. In unserem Beispiel erfolgt dies in der Zeile 
for _, observer := range videoPublishedChannel .
Jetzt ist es Zeit, Ihr Abonnement zu überprüfen.
Abonnement überprüfenGraphQL hat natürlich einige wertvolle Fähigkeiten, aber wie sie sagen, ist nicht alles, was glänzt, Gold. Wir sprechen nämlich über die Tatsache, dass sich jemand, der GraphQL verwendet, um die Autorisierung, die Komplexität von Anforderungen, das Caching, das Problem von N + 1-Anforderungen, die Begrenzung der Geschwindigkeit der Abfrageausführung und einige andere Dinge kümmern muss. Andernfalls kann ein mit GraphQL entwickeltes System einem erheblichen Leistungsabfall ausgesetzt sein.
Fortgeschrittene Techniken: Authentifizierung, Datenlader, Abfragekomplexität
Jedes Mal, wenn ich solche Handbücher lese, habe ich das Gefühl, dass ich, nachdem ich sie beherrsche, alles lerne, was ich über eine bestimmte Technologie wissen muss, und die Fähigkeit bekomme, Probleme jeder Komplexität zu lösen.
Wenn ich jedoch anfange, an meinen eigenen Projekten zu arbeiten, gerate ich normalerweise in unvorhergesehene Situationen, die wie Serverfehler oder Anforderungen aussehen, die seit Ewigkeiten ausgeführt werden, oder wie andere Deadlock-Situationen. Um dies zu tun, muss ich mich daher besser mit dem befassen, was in letzter Zeit vollkommen verständlich schien. In diesem Handbuch hoffe ich, dass dies vermieden werden kann. Aus diesem Grund werden wir uns in diesem Abschnitt mit einigen fortgeschrittenen Techniken für die Arbeit mit GraphQL befassen.
▍ Authentifizierung
Bei der Arbeit mit der REST-API verfügen wir über ein Authentifizierungssystem und Standardautorisierungstools, wenn Sie mit einem bestimmten Endpunkt arbeiten. Bei Verwendung von GraphQL wird jedoch nur ein Endpunkt verwendet. Daher können Authentifizierungsaufgaben mithilfe von Schemaanweisungen gelöst werden. Bearbeiten Sie die Datei 
schema.graphql wie folgt:
 type Mutation {   createVideo(input: NewVideo!): Video! @isAuthenticated } directive @isAuthenticated on FIELD_DEFINITION 
Wir haben die 
isAuthenticated Direktive erstellt und auf das 
createVideo Abonnement 
createVideo . Nach der nächsten Sitzung zur automatischen Codegenerierung müssen Sie eine Definition für diese Anweisung definieren. Jetzt werden Direktiven in Form von Methoden von Strukturen implementiert und nicht in Form von Schnittstellen, daher müssen wir sie beschreiben. Ich habe den automatisch generierten Code in der Datei 
server.go bearbeitet und eine Methode erstellt, die die GraphQL-Konfiguration für die Datei 
server.go . Hier ist die Datei 
resolver.go :
 func NewRootResolvers(db *sql.DB) Config { c := Config{   Resolvers: &Resolver{     db: db,   }, }  
Hier ist die Datei 
server.go :
 rootHandler:= dataloaders.DataloaderMiddleware(   db,   handler.GraphQL(     go_graphql_demo.NewExecutableSchema(go_graphql_demo.NewRootResolvers(db)   ) ) http.Handle("/query", auth.AuthMiddleware(rootHandler)) 
Wir lesen die Benutzer- 
ID aus dem Kontext. Findest du das nicht seltsam? Wie kam diese Bedeutung in den Kontext und warum erschien sie überhaupt im Kontext? Tatsache ist, dass gqlgen Anforderungskontexte nur auf Implementierungsebene bereitstellt, sodass wir keine Möglichkeit haben, HTTP-Anforderungsdaten wie Header oder Cookies in Erkennern oder Anweisungen zu lesen. Daher müssen Sie dem System Ihre eigenen Zwischenmechanismen hinzufügen, diese Daten empfangen und in einen Kontext setzen.
Jetzt müssen wir unseren eigenen Zwischenauthentifizierungsmechanismus beschreiben, um Authentifizierungsdaten aus der Anforderung zu erhalten und diese zu überprüfen.
Hier ist keine Logik definiert. Stattdessen wird zu Berechtigungsdaten zu Demonstrationszwecken die Benutzer- 
ID einfach hier übergeben. Dieser Mechanismus wird dann in 
server.go mit einer neuen Konfigurationslademethode kombiniert.
Nun macht die Beschreibung der Richtlinie Sinn. Wir verarbeiten keine nicht autorisierten Benutzeranfragen im Middleware-Code, da solche Anfragen von der Direktive verarbeitet werden. So sieht es aus.
Arbeiten Sie mit einem nicht autorisierten BenutzerArbeiten Sie mit einem autorisierten BenutzerWenn Sie mit Schema-Direktiven arbeiten, können Sie sogar Argumente übergeben:
 directive @hasRole(role: Role!) on FIELD_DEFINITION enum Role { ADMIN USER } 
▍Datenlader
Es scheint mir, dass das alles ziemlich interessant aussieht. Sie laden Daten herunter, wenn Sie sie benötigen. Clients haben die Möglichkeit, Daten zu verwalten. Genau das, was benötigt wird, wird aus dem Speicher entnommen. Aber alles hat einen Preis.
Was ist der Preis für diese Möglichkeiten? Schauen Sie sich die Download-Protokolle aller Videos an. Wir sprechen nämlich über die Tatsache, dass wir 8 Videos und 5 Benutzer haben.
 query{ Videos(limit: 10){   name   user{     name   } } } 
Video-Download-Details 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 
Was ist hier los? Warum gibt es 9 Anfragen (1 Anfrage ist der Videotabelle zugeordnet und 8 - der Benutzertabelle)? Es sieht schrecklich aus. Mein Herz blieb fast stehen, als ich dachte, dass unsere vorhandene API durch diese ersetzt werden müsste ... Richtig, Datenlader können dieses Problem vollständig bewältigen.
Dies ist als N + 1-Problem bekannt. Wir sprechen über die Tatsache, dass es eine Abfrage gibt, um alle Daten abzurufen, und für jedes Datenelement (N) gibt es eine andere Abfrage an die Datenbank.
Dies ist ein sehr ernstes Problem in Bezug auf Leistung und Ressourcen: Obwohl diese Anforderungen parallel sind, belasten sie die Systemressourcen.
Um dieses Problem zu lösen, verwenden wir die 
dataloaden- Bibliothek des Autors der gqlgen-Bibliothek. Mit dieser Bibliothek können Sie Go-Code generieren. Generieren Sie zunächst einen Datenlader für die 
User :
 go get github.com/vektah/dataloaden dataloaden github.com/ridhamtarpara/go-graphql-demo/api.User 
Wir verfügen über die Datei 
userloader_gen.go , die Methoden wie 
Fetch , 
LoadAll und 
Prime .
Um allgemeine Ergebnisse zu erhalten, müssen wir nun die 
Fetch Methode (Datei 
dataloader.go ) definieren:
 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) }) } 
Hier warten wir 1 ms. bevor Sie die Anforderung ausführen und die Anforderungen in Paketen mit bis zu 100 Anforderungen sammeln. Anstatt jetzt eine Anforderung für jeden Benutzer einzeln auszuführen, wartet der Loader jetzt auf die angegebene Zeit, bevor er auf die Datenbank zugreift. Als Nächstes müssen Sie die Erkennungslogik ändern, indem Sie sie mithilfe der Anforderung zur Verwendung des Datenladers (Datei 
resolver.go ) neu konfigurieren:
 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 } 
So sehen die Protokolle danach in einer ähnlichen Situation wie oben beschrieben aus:
 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) 
Hier werden nur zwei Datenbankabfragen ausgeführt, sodass alle zufrieden sind. Es ist interessant festzustellen, dass nur 5 Benutzer-IDs an die Anforderung gesendet werden, obwohl Daten für 8 Videos angefordert werden. Dies deutet darauf hin, dass der Datenlader doppelte Datensätze entfernt.
▍
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 . , , , , .
Zusammenfassung
, , 
GitHub . . , , .
Liebe Leser! GraphQL , Go?
