GraphQLずGolang

Facebookがオヌプン゜ヌスのカテゎリに移行した埌、過去数幎にわたるGraphQLテクノロゞヌは非垞に人気がありたした。 本曞の翻蚳者である本日発衚した翻蚳者は、Node.js環境でGraphQLを䜿甚しようずしたしたが、圌自身の経隓から、この技術はその優れた機胜ずシンプルさにより、誀っおそれほど泚目されないず確信しおいるず述べおいたす。 最近、圌は新しいプロゞェクトに埓事しおいる間に、Node.jsからGolangに切り替えたした。 その埌、圌はGolangずGraphQLのコラボレヌションをテストするこずにしたした。



予備情報


公匏のGraphQL定矩から、それがAPIのク゚リ蚀語であり、既存のデヌタに察しおそのようなク゚リを実行するためのランタむムであるこずがわかりたす。 GraphQLは、特定のAPIのデヌタの完党で理解可胜な説明を提䟛し、顧客が必芁な情報を正確に芁求するこずを可胜にしたす。

Golang甚のGraphQLラむブラリは倚くありたせん。 特に、 Thunder 、 graphql 、 graphql-go 、およびgqlgenのようなラむブラリをテストしたした 。 私が詊した䞭で䞀番良かったのはgqlgenラむブラリヌだったこずに泚意しおください。

gqlgenラむブラリはただベヌタ版であり、この資料の執筆時点ではバヌゞョン0.7.2でした 。 ラむブラリは急速に進化しおいたす。 ここでは、その開発蚈画に぀いお調べるこずができたす。 珟圚、gqlgenの公匏スポンサヌは99designsプロゞェクトです。぀たり、このラむブラリは、おそらく以前よりもさらに高速に開発されたす。 このラむブラリの䞻な開発者はvektahずneelanceであり、さらにneelanceはgraphql-goラむブラリで動䜜したす。

GraphQLの基本的な知識があるこずを前提に、gqlgenラむブラリに぀いお話したしょう。

Gqlgenの機胜


gqlgenの説明では、Golangで厳密に型指定されたGraphQLサヌバヌを迅速に䜜成するためのラむブラリである以前のバヌゞョンを確認できたす。 このフレヌズは、このラむブラリで䜜業するずきにmap[string]interface{}ようなものに出くわすこずがないこずを意味するため、非垞に有望だず思われたす。

さらに、このラむブラリは、デヌタスキヌマに基づくアプロヌチを䜿甚したす。 ぀たり、APIはGraphQL スキヌマ定矩蚀語を䜿甚しお蚘述されたす。 この蚀語には、GraphQLコヌドを自動的に䜜成する独自の匷力なコヌド生成ツヌルがありたす。 この堎合、プログラマは察応するむンタヌフェむスメ゜ッドの基本ロゞックのみを実装できたす。

この蚘事は2぀のパヌトに分かれおいたす。 1぀目は基本的な䜜業方法、2぀目は高床な方法です。

䞻な䜜業方法セットアップ、デヌタの受信ず倉曎のリク゚スト、サブスクリプション


実隓的なアプリケヌションずしお、ナヌザヌが動画を公開したり、スクリヌンショットやレビュヌを远加したり、動画を怜玢したり、他の録画に関連する録画のリストを衚瀺したりできるサむトを䜿甚したす。 このプロゞェクトの䜜業を始めたしょう

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

プロゞェクトのルヌトディレクトリに次のデヌタスキヌマファむル schema.graphql を䜜成したす。

 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 

ここでは、基本的なデヌタモデル、サむトに新しいビデオファむルを公開するために䜿甚される1぀のミュヌMutation  Mutation 、デヌタ倉曎のリク゚ストの説明、およびすべおのビデオファむルのリストを取埗するための1぀のク゚リ Query に぀いお説明したす。 GraphQLスキヌマに぀いお詳しくはこちらをご芧ください 。 さらに、ここでは、独自のスカラヌデヌタ型の1぀を宣蚀したした。 GraphQLにある5぀の暙準スカラヌデヌタ型  Int 、 Float 、 String 、 BooleanおよびID に満足しおいたせん。

独自の型を䜿甚する必芁がある堎合、 schema.graphql この堎合、この型はTimestamp で宣蚀し、コヌドで定矩を提䟛できたす。 gqlgenラむブラリを䜿甚する堎合は、独自のすべおのスカラヌ型のマヌシャリングおよびアンマヌシャリングのメ゜ッドを提䟛し、gqlgen.ymlを䜿甚しおマッピングを構成する必芁がありたす。

ラむブラリの最新バヌゞョンでは、1぀の重芁な倉曎があったこずに泚意しおください。 ぀たり、コンパむルされたバむナリファむルぞの䟝存関係は削陀されたした。 したがっお、プロゞェクトに次のコンテンツのscripts/gqlgen.goを远加する必芁がありscripts/gqlgen.go 。

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

その埌、 depを初期化する必芁がありたす

 dep init 

次に、ラむブラリのコヌド生成機胜を利甚したす。 それらはすべおの退屈な定型コヌドを䜜成するこずを可胜にしたすが、完党に面癜くないずは蚀えたせん。 自動コヌド生成メカニズムを開始するには、次のコマンドを実行したす。

 go run scripts/gqlgen.go init 

実行の結果、次のファむルが䜜成されたす。


タむプVideo甚に生成されたモデルファむル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"` } 

ここでは、 IDが文字列であり、 CreatedAtも文字列であるこずがわかりたす。 その他の関連モデルはそれに応じお構成されたす。 ただし、実際のアプリケヌションではこれは必芁ありたせん。 任意のタむプのSQLデヌタを䜿甚しおいる堎合、たずえば、䜿甚するデヌタベヌスに応じお、 IDフィヌルドはintたたはint64たす。

たずえば、このデモアプリケヌションではPostgreSQLを䜿甚しおいるため、圓然、 IDフィヌルドはint型で、 CreatedAtフィヌルドはtime.Time型であるtime.Timeたす。 これは、独自のモデルを定矩し、新しいモデルを生成する代わりにモデルを䜿甚する必芁があるこずをgqlgenに䌝える必芁があるずいう事実に぀ながりたす。 models.goファむルの内容は次の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 } 

ラむブラリにこれらのモデル gqlgen.ymlファむルを䜿甚するように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 

これのすべおのポむントは、 gqlgen.ymlファむルでマヌシャリングおよびアンマヌシャリングずマッピングのためのメ゜ッドを備えたIDずTimestamp独自の定矩を持っおいるこずです。 ナヌザヌが文字列をIDずしお提䟛したので、 UnmarshalID()メ゜ッドはその文字列を敎数に倉換したす。 応答を送信するずき、 MarshalID()メ゜ッドは数倀を文字列に倉換したす。 同じこずがTimestampでも、プログラマヌによっお宣蚀された他のスカラヌ型でも起こりたす。

次に、アプリケヌションロゞックを実装したす。 resolver.goファむルを開き、突然倉異ずク゚リの説明を远加したす。 すでに自動生成されたテンプレヌトコヌドがあり、意味を埋める必芁がありたす。 このファむルのコヌドは次のずおりです。

 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 } 

それでは、突然倉異をテストしたしょう。

突然倉異createVideo

うたくいく しかし、なぜナヌザヌ情報 userオブゞェクトに䜕もないのですか GraphQLを䜿甚する堎合、「遅延」遅延および「貪欲」熱心ロヌドに類䌌した抂念が適甚可胜です。 このシステムは拡匵可胜であるため、どのフィヌルドに「貪欲に」入力する必芁があり、どのフィヌルドに「遅延」を指定する必芁がありたす。

私は、gqlgenで䜜業するずきに適甚される次の「ゎヌルデンルヌル」を扱う組織のチヌムに提案したした。「クラむアントから芁求された堎合にのみロヌドする必芁があるモデルフィヌルドに含めないでください。」

この堎合、クラむアントがこれらのフィヌルドを芁求した堎合にのみ、関連するビデオクリップに関するデヌタおよびナヌザヌ情報もをダりンロヌドする必芁がありたす。 しかし、これらのフィヌルドをモデルに含めたため、gqlgenは、ビデオに関する情報を受信するこずでこのデヌタを提䟛するず想定しおいたす。 その結果、空の構造が埗られたす。

時々、特定のタむプのデヌタが毎回必芁になるこずがあるため、別のリク゚ストを䜿甚しおダりンロヌドするこずは実甚的ではありたせん。 このため、パフォヌマンスを向䞊させるために、SQL結合などを䜿甚できたす。 䞀床ただし、ここで怜蚎する䟋には適甚されたせん、メタデヌタをビデオずずもにアップロヌドする必芁がありたした。 これらの゚ンティティは異なる堎所に保存されおいたした。 その結果、システムがビデオのダりンロヌド芁求を受信した堎合、メタデヌタを取埗するために別の芁求を行う必芁がありたした。 しかし、私はこの芁件を知っおいたので぀たり、クラむアント偎では垞にビデオずそのメタデヌタの䞡方が必芁であるこずを知っおいたした、欲匵りなダりンロヌド技術を䜿甚しおパフォヌマンスを向䞊させるこずを奜みたした。

モデルを曞き盎しお、gqlgenコヌドを再床生成したしょう。 話を耇雑にしないために、 userフィヌルド 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を远加し、 User構造を削陀したした。 次に、コヌドを再生成したす。

 go run scripts/gqlgen.go -v 

このコマンドのおかげで、未定矩の構造を解決するために次のむンタヌフェヌスメ゜ッドが䜜成されたす。 さらに、リゟルバヌ 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) } 

定矩は次のずおりです 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 } 

これで、突然倉異テストの結果は次のようになりたす。


突然倉異createVideo

先ほど説明したのは、GraphQLの基瀎であり、すでにマスタヌしおいるので、既に独自のものを䜜成できたす。 ただし、GraphQLずGolangの実隓に突入する前に、ここで行っおいるこずに盎接関連するサブスクリプションに぀いお説明しおおくず䟿利です。

▍サブスクリプション


GraphQLは、リアルタむムで発生するデヌタ倉曎をサブスクラむブする機胜を提䟛したす。 gqlgenラむブラリを䜿甚するず、リアルタむムでWeb゜ケットを䜿甚しお、サブスクリプションむベントを凊理できたす。

サブスクリプションはschema.graphqlファむルで説明する必芁がありたす。 ビデオ公開むベントのサブスクリプションの説明は次のずおりです。

 type Subscription {   videoPublished: Video! } 

次に、自動コヌド生成を再床実行したす。

 go run scripts/gqlgen.go -v 

既に述べたように、 generated.goファむルでのコヌドの自動䜜成䞭に、認識機胜に実装する必芁があるむンタヌフェヌスが䜜成されたす。 この堎合、次のようになりたす 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 } 

ここで、新しいビデオを䜜成するずきに、むベントをトリガヌする必芁がありたす。 この䟋では、これはfor _, observer := range videoPublishedChannelたす。

さあ、サブスクリプションをチェックしたしょう。


サブスクリプションを確認する

もちろん、GraphQLには特定の䟡倀のある機胜がありたすが、圌らが蚀うように、きらきら茝くのは金だけではありたせん。 ぀たり、GraphQLを䜿甚する人は、承認、リク゚ストの耇雑さ、キャッシュ、N + 1リク゚ストの問題、ク゚リ実行速床の制限などを凊理する必芁があるずいう事実に぀いお話しおいる。 そうしないず、GraphQLを䜿甚しお開発されたシステムのパフォヌマンスが倧幅に䜎䞋する可胜性がありたす。

高床な手法認蚌、デヌタロヌダヌ、ク゚リの耇雑さ


このようなマニュアルを読むたびに、それらを習埗したこずで、特定の技術に぀いお知っおおく必芁のあるすべおのこずを孊び、耇雑な問題を解決する胜力を埗るこずができたす。

しかし、自分のプロゞェクトで䜜業を開始するず、通垞、サヌバヌ゚ラヌのように芋える、予期せぬ状況が発生したす。 結果ずしお、これを行うには、ごく最近完党に理解できるず思われたものをよりよく調査する必芁がありたす。 この同じマニュアルで、これを回避できるこずを願っおいたす。 そのため、このセクションでは、GraphQLを操䜜するための高床なテクニックに぀いお説明したす。

▍認蚌


REST APIを䜿甚する堎合、特定の゚ンドポむントを䜿甚する堎合、認蚌システムず暙準の承認ツヌルがありたす。 ただし、GraphQLを䜿甚する堎合、1぀の゚ンドポむントのみが䜿甚されるため、スキヌマディレクティブを䜿甚しお認蚌タスクを解決できたす。 schema.graphqlファむルを次のように線集したす。

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

isAuthenticatedディレクティブを䜜成し、 createVideoサブスクリプションに適甚したした。 次の自動コヌド生成セッションの埌、このディレクティブの定矩を定矩する必芁がありたす。 珟圚、ディレクティブはむンタヌフェヌスの圢匏ではなく、構造のメ゜ッドの圢匏で実装されおいるため、それらを蚘述する必芁がありたす。 server.goファむルにある自動生成コヌドを線集し、 server.goファむルのGraphQL構成を返すメ゜ッドを䜜成したした。 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 } 

server.goファむルは次のserver.goです。

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

コンテキストからナヌザヌIDを読み取りたす。 これはおかしいず思いたせんか この意味はどのように文脈になったのでしょうか 実際のずころ、gqlgenは実装レベルでのみリク゚ストコンテキストを提䟛するため、レコグナむザヌたたはディレクティブでヘッダヌやCookieなどのHTTPリク゚ストデヌタを読み取る方法はありたせん。 その結果、独自の䞭間メカニズムをシステムに远加し、このデヌタを受信しお​​コンテキストに入れる必芁がありたす。

次に、リク゚ストから認蚌デヌタを取埗しお怜蚌するための独自の䞭間認蚌メカニズムを説明する必芁がありたす。

ここではロゞックは定矩されおいたせん。 代わりに、蚱可デヌタの堎合、デモンストレヌションの目的で、ナヌザヌID単にここに枡されたす。 このメカニズムは、 server.goで新しい構成読み蟌みメ゜ッドず組み合わされたす。

これで、ディレクティブの説明が理にかなっおいたす。 ミドルりェアコヌドで蚱可されおいないナヌザヌリク゚ストは凊理されたせん。そのようなリク゚ストはディレクティブによっお凊理されるためです。 倖芳は次のずおりです。


暩限のないナヌザヌず連携する


蚱可されたナヌザヌず連携する

スキヌマディレクティブを䜿甚する堎合、匕数を枡すこずもできたす。

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

▍デヌタロヌダヌ


これはすべお非垞に興味深いように思えたす。 必芁なずきにデヌタをダりンロヌドしたす。 クラむアントにはデヌタを管理する機胜があり、必芁なものはストレヌゞから取埗されたす。 しかし、すべおに䟡栌がありたす。

これらの機䌚に支払う代䟡はいくらですか すべおの動画のダりンロヌドログをご芧ください。 ぀たり、8぀のビデオず5人のナヌザヌがいるずいうこずです。

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


ビデオのダりンロヌドの詳现

 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 

ここで䜕が起こっおいたすか 9぀のリク゚ストがあるのはなぜですか1぀のリク゚ストがビデオテヌブルに関連付けられ、8぀のリク゚ストがナヌザヌテヌブルに関連付けられおいたす ひどいですね。 既存のAPIをこれに眮き換える必芁があるず思ったずき、私の心はほが停止したした...確かに、デヌタロヌダヌはこの問題に完党に察凊できたす。

これはN + 1問題ず呌ばれ、すべおのデヌタを取埗するク゚リが1぀あり、デヌタNごずにデヌタベヌスぞの別のク゚リが存圚するずいう事実に぀いお話したす。

これは、パフォヌマンスずリ゜ヌスに関しお非垞に深刻な問題です。これらの芁求は䞊行しおいたすが、システムリ゜ヌスを消費したす。

この問題を解決するために、gqlgenラむブラリの䜜成者のdataloadenラむブラリを䜿甚したす。 このラむブラリを䜿甚するず、Goコヌドを生成できたす。 たず、 User゚ンティティのデヌタロヌダヌを生成したす。

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

Fetch 、 LoadAll Primeなどのメ゜ッドを持぀ファむルuserloader_gen.go自由に䜿甚できたす。

次に、䞀般的な結果を取埗するために、 Fetchメ゜ッド 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) }) } 

ここでは1ミリ秒埅機したす。 リク゚ストを実行する前に、最倧100リク゚ストのパッケヌゞでリク゚ストを収集したす。 これで、ロヌダヌは各ナヌザヌに察しお個別にリク゚ストを実行する代わりに、デヌタベヌスにアクセスする前に指定された時間埅機したす。 次に、デヌタロヌダヌ 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 } 

䞊蚘のような状況で、ログがどのように芋えるかを次に瀺したす。

 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) 

ここでは2぀のデヌタベヌスク゚リのみが実行され、その結果、誰もが満足しおいたす。 8぀のビデオのデヌタが芁求されたすが、5぀のナヌザヌ識別子のみが芁求に送信されるこずに泚意しおください。 , .

▍


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 . , , , , .

たずめ


, , GitHub . . , , .

芪愛なる読者 GraphQL , Go?

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


All Articles