im using gqlgen lib to implement a graph ql server .. the following setup code is straigthforward
port := os.Getenv("PORT")
if port == "" {
port = defaultPort
}
graphCfg := graph.Config{
Resolvers: graph.NewRootResolver(),
// TODO: add complexity calc and shield
//Complexity: graph.ComplexityRoot{},
//Directives: graph.DirectiveRoot{},
}
graphSchema := graph.NewExecutableSchema(graphCfg)
var gqlSrv http.Handler
gqlSrv = handler.NewDefaultServer(graphSchema)
gqlSrv = factory.Middleware(gqlSrv)
http.Handle("/graphql", gqlSrv)
Root Resolver:
package graph
import (
communicationResolvers "github.com/venn-city/service-venn-graph/modules/communication/resolvers"
realEstateResolvers "github.com/venn-city/service-venn-graph/modules/realestate/resolvers"
socialResolvers "github.com/venn-city/service-venn-graph/modules/social/resolvers"
)
// Generate gql models
//go:generate go run github.com/99designs/gqlgen generate
// This file will not be regenerated automatically.
// It serves as dependency injection for your app, add any dependencies you require here.
type RootResolver struct{}
func NewRootResolver() *RootResolver {
return &RootResolver{}
}
as the comment states // It serves as dependency injection for your app, add any dependencies you require here.
the issue is that RootResolver is created once for the entire application lifetime .. it would have been much simpler if i had the ability to create a scoped IOC container per request ..
My current approach is to write a custom middleware / handler that will create the executable schema per-request so that RootResolver will be create as new per request …
as an example I would like to create a logger with a RequetId field and pas that logger to all resolvers and lower levels of logic to use same logger.
would very much appreciate any insights on that ! thanks !
Recreating the root resolver at each request isn't a good idea. The dependency injection the code comment talks about is service-scoped dependencies. Stuff that you need to set into the root resolver once. For example configuration values, caches, etc.
If you need request-scoped dependencies, use gqlgen middlewares with the
Around*methods available on theServertype. These middleware functions get acontext.Contextas first argument, you can set request-scoped values in there.The gqlgen source contains a code comment block that explains how the request lifecycle works (author: Brandon Sprague):
AroundOperationsis called before eachquery,mutationorsubscriptionoperation.AroundResponsesis called before returning the responseAroundRootFieldsis called before entering each root resolver. In the query example above, it's called before theviewerresolver.AroundFieldsis called before each field resolver.In case of a logger, you might use
AroundOperations. You can compute whatever value you need in the middleware itself, or use values initialized in the enclosing scope.Then in your resolver implementations, you can access those values from the context as usual:
ctx.Value("logger-key"). (Note that instead of strings, it's better to use unexported structs as context keys).