-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Example to use with graphql-go #19
Comments
@vietthang definitely not. This library is meant to be orthogonal to any other library, but I can give you an example of how you could use them together. One way to do it is to attach the data loaders to the request context, and extract them from the context when you need them in your resolver (since each resolver has I'm currently using the library this way, but I suspect it's an abuse of the request context and that there's a much better way to pass the data loaders around. ExampleGraphQL schemaquery {
# Get a thing by it's ID.
thing(id: ID!): Thing
}
# Thing is pretty simple.
type Thing {
id: ID
name: String
} Loader// ThingsLoader probably holds whatever we will use to load Things.
type ThingsLoader struct {
client things.Client
}
func (l *ThingsLoader) Attach(ctx context.Context) dataloader.BatchFunc {
return func(ids []string) []*dataloader.Result {
resp, err := l.client.Things(ctx, ids)
if err != nil {
// return a `[]*dataloader.Result` with `len(ids)` elements that have errors.
}
// do any work you need to do to get the response in the same order as the input `ids`
results := []*dataloader.Result{}
for _, thing := range resp.Things {
results = append(results, thing)
}
return results, nil
}
} Resolvers// ThingResolver holds information about the thing we are resolving.
type ThingResolver struct {
id string
loader *dataloader.Loader // we'll get this from the request context
}
// ThingArgs holds the arguments you pass into the `thing` query.
type ThingArgs struct {
ID graphql.ID
}
// Thing resolves the query `thing`.
func (r *QueryResolver) Thing(ctx context.Context, args *ThingArgs) (*ThingResolver, error) {
// Here we are extracting the loader that we've placed on the context at the
// beginning of the request, and asserting the type of the value is `*dataloader.Loader`.
loader, found := ctx.Value("thing loader").(*dataloader.Loader)
if !found {
return nil, errors.New("unable to find the thing loader")
}
return &ThingResolver{id: args.ID, loader: loader}, nil
}
// ID is the Thing's identifier.
// We already have it, so let's not load any data.
func (r *ThingResolver) ID() *graphql.ID {
id := graphql.ID(r.ID)
return &id
}
// Name is what we call this Thing.
// We don't know it yet, so we have to load it.
func (r *ThingResolver) Name() (*string, error) {
thunk := r.loader.Load(r.ID)
data, err := thunk()
if err != nil {
return nil, fmt.Errorf("thing.name: %v", err)
}
name, ok := data.(*string)
if !ok {
return nil, fmt.Errorf("thing.name: loaded the wrong type of data: %#v", data)
}
return name, nil
} Obviously if you are loading data in multiple resolvers, you'll want to pull that code into it's own function, but that should give you an idea. Tying it all togetherfunc handler(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
thingLoader := &ThingLoader{client: thing.NewClient()}
batchFunc := thingLoader.Attach(ctx)
loader := dataloader.NewBatchedLoader(batchFunc)
ctx = context.WithValue(ctx, "thing loader", loader)
// Execute the query against your schema and resolver
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":12345", nil))
} Another way you could go is to initialize the data loader where you need it (and possibly cache the instance if it's expensive to initialize the loader). Maybe a better way would be to have some data structure with all your loaders, and then pass around a reference to that data structure in your resolvers. I'm 95% sure the example I gave you is not the best approach to passing loaders around. I'll get a chance to revisit other implementations sooner than later, but I encourage you to explore a few patterns, see what works for you, and share your results 😄 |
Thank you very much for your detailed answer. I will try your approach and definitely will share if I come up with any idea. Thanks. |
For those using graphql-go, this approach doesn't work (as of this date) because graphql-go resolves field serially. Follow graphql-go/graphql#106 |
@gburt Do you have any idea how the parallel field resolving thing in graphql-go go? I had to drop using graphql-go and use node.js because of this issue a quite time ago. |
@gburt @vietthang You can try https://github.com/qdentity/graphql-go, but please be aware I'm not maintaining this fork. I took the changes from graphql-go/graphql#213 and added the concurrent resolving of list values graphql-go/graphql#132. There were some data races but I managed to fix these as well, all tests are passing with the -race flag included. Currently we went for https://github.com/graph-gophers/graphql-go as it supports dataloader out-of-the-box (it resolves fields already concurrently). |
What is this thing about this NewClient? What purpose does it serve? I am trying to grasp it. type ThingsLoader struct {
client things.Client
} |
@smithaitufe I think that is supposed to represent a client to load information about |
For those looking for a full working example, you might want to take a look to: https://github.com/graphql-go/graphql-dataloader-sample |
I'm building a project with graphql-go and need a dataloader implementation. After a bit research, I found your library and seems like you build this with graphql-go support in mind. But I don't understand how to use 2 libraries together. Can you help me with some sample code or ideas how they work together. Do I need to modify graphql-go source code to use this library?
Thank you.
The text was updated successfully, but these errors were encountered: