How do you handle optionally calling field-level resolvers / resolver chains? #1996
Replies: 2 comments 1 reply
-
Looking at graphql-go it seems to support this field-level resolvers tree and looking over these gqlgen docs again it seems like binding to a method is part of how this is to be handled. I found a repo which seems to pre-define the resolvers and then alert gqlgen to look for them. |
Beta Was this translation helpful? Give feedback.
-
So in my experience using gqlgen so far, the easiest way is to specify a custom model and then just omit the field(s) you want to have as a resolver. So when you do something like this in a separate package models
// After auto generating the model, move it to a separate location
// For the fields we want a resolver generated for, comment them out
type Author struct {
Name string `json:"name"`
}
type Book struct {
Title string `json:"title"`
// Author *Author `json:"author"`
}
type Library struct {
Branch string `json:"branch"`
// Books []*Book `json:"books"`
} You will get a package graph
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"fmt"
"main/graph/generated"
"main/models"
)
func (r *bookResolver) Author(ctx context.Context, obj *models.Book) (*models.Author, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *libraryResolver) Books(ctx context.Context, obj *models.Library) ([]*models.Book, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Libraries(ctx context.Context) ([]*models.Library, error) {
panic(fmt.Errorf("not implemented"))
}
// Book returns generated.BookResolver implementation.
func (r *Resolver) Book() generated.BookResolver { return &bookResolver{r} }
// Library returns generated.LibraryResolver implementation.
func (r *Resolver) Library() generated.LibraryResolver { return &libraryResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type bookResolver struct{ *Resolver }
type libraryResolver struct{ *Resolver }
type queryResolver struct{ *Resolver } And so when you do a query like you mentioned {
libraries {
branch
}
} Only the And then if you do a query like this {
libraries {
branch
books {
title
}
}
} The Hopefully this answers your question |
Beta Was this translation helpful? Give feedback.
-
It's common to have nested resolvers / field level resolvers which are only called if requested by the client.
Chaining resolvers you can answer queries like
But if the client only asks for the top-level item, no further resolvers are called. In the following query, the "books" resolver is never run.
Instead, it seems like gqlgen leaves it up to the implementor to figure out if additional resolvers should be called. This seems odd as it makes sense the other frameworks handle this for the implementor so they can avoid polluting each resolver with a bunch of conditionals to optionally call the other resolvers.
It seems like the With*Context() methods might help with this, but I'm concerned they require a lot of conditions and manual resolver calls for well-linked items like users / accounts.
What is the recommended approach to this common issue of nested resolvers being optionally called if requested?
Beta Was this translation helpful? Give feedback.
All reactions