-
Notifications
You must be signed in to change notification settings - Fork 845
Description
Concurrent Resolvers
-
At the moment we don't have built-in functionality to run resolvers concurrently.
-
We want to integrate built-in, opt-in, performant and simple solution to run resolvers concurrently.
Current Implementation
- Resolvers are executed synchronously. Therefore not possible to run Goroutines within resolvers.
Current alternative solutions
- Run Goroutines before
graphql.Do
:- Execute async work based on requested fields using graphql's
visitor
and pass resolved data asRootObject
or viacontext
. This might be helpful solution even after integrating concurrent resolvers support to the lib, a working example explaining this solution can be found in the following gist: https://gist.github.com/chris-ramon/78027c8c0b283bfd1d20d6d989485e1d
- Execute async work based on requested fields using graphql's
Proposed solutions
Note: Thunk
is a function that is returned from a function.
1) Resolver returns a Thunk
✔️ PR: #388
✔️ Opt-in — Via thunks
✔️ Performant — No additional goroutines
✔️ Simple — Uses existing public API
✔️ Up-to-date
Example:
"pullRequests": &graphql.Field{
Type: graphql.NewList(PullRequestType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
ch := make(chan []PullRequest)
// Concurrent work via Goroutines.
go func() {
// Async work to obtain pullRequests.
ch <- pullRequests
}()
return func() interface{} {
return <-ch
}, nil
},
},
2) Resolver returns a Goroutined Thunk
✔️ PR: #213
✔️ Opt-in — Via thunks
❗ Performant — Goroutine per resolver
✔️ Simple — Uses existing public API
✔️ Up-to-date
Example:
"pullRequests": &graphql.Field{
Type: graphql.NewList(PullRequestType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
ch := make(chan []PullRequest)
// Concurrent work via Goroutines.
go func() {
// Async work to obtain pullRequests.
ch <- pullRequests
}()
return func() (interface{}, error) {
return <-ch, nil
}, nil
},
},
3) Resolver returns a Channel
✔️ PR: #357
✔️ Opt-in — via new public APIs
✔️ Performant — No additional goroutines
❗ Simple — New set of JavaScript promises-like public APIs
✔️ Up-to-date
Example:
ch := make(chan []PullRequest)
// Concurrent work via Goroutines.
// Async work to obtain pullRequests.
graphql.Do(graphql.Params{
Schema: schema,
RequestString: tc.RequestString,
IdleHandler: func() {
ch <- &graphql.ResolveResult{
Value: pullRequests,
}
},
})
// ...
"pullRequests": &graphql.Field{
Type: graphql.NewList(PullRequestType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return graphql.ResolvePromise(ch), nil
},
},
4) Resolver's field flag
✔️ PR: sogko#23
✔️ Opt-in — via Field.Parallel
❗ Performant — Goroutine per resolver
✔️ Simple — Uses existing public API
❗ Up-to-date
Example:
"pullRequests": &graphql.Field{
Type: graphql.NewList(PullRequestType),
Parallel: true,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
ch := make(chan []PullRequest)
// Concurrent work via Goroutines.
go func() {
// Async work to obtain pullRequests.
ch <- pullRequests
}()
return <-ch, nil
},
},
5) Built-in Goroutined Resolver
✔️ PR: #132
❗ Opt-in — Built-in support
❗ Performant — Goroutine per resolver
✔️ Simple — Uses existing public API
❗ Up-to-date
Example:
"pullRequests": &graphql.Field{
Type: graphql.NewList(PullRequestType),
// Resolve runs async
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
var pullRequests []PullRequest
// Async work to obtain pullRequests
return pullRequests, nil
},
},
6) Goroutined Resolver via Dataloader
✔️ PR: #154
✔️ Opt-in — Via new public APIs
✔️ Performant — No additional goroutines
❗ Simple — New public APIs
❗ Up-to-date
Example:
dl := dataloader.DataLoader{
pullRequestsLoader: dataloader.New(sch, dataloader.Parallel(func(key interface{}) dataloader.Value {
var pullRequests []PullRequest
// Async work to obtain pullRequests
return dataloader.NewValue(pullRequests, nil)
})),
}
var loaderKey = struct{}{}
ctx := context.WithValue(parent, loaderKey, dl)
graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
Context: ctx,
Executor: &executor,
})