gqlgen和使用Golang的依赖注入

huangapple go评论92阅读模式
英文:

gqlgen and dependency injection using golang

问题

我正在使用gqlgen库来实现一个GraphQL服务器。以下是设置代码:

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)

根解析器:

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{}
}

正如注释所述// It serves as dependency injection for your app, add any dependencies you require here.,这个文件用于为您的应用程序进行依赖注入,您可以在此处添加所需的任何依赖项。

问题在于RootResolver在整个应用程序生命周期中只创建一次。如果我能够每个请求创建一个作用域的IOC容器,那将会更简单。

我的当前方法是编写一个自定义的中间件/处理程序,每个请求都会创建一个可执行的模式,这样RootResolver将会在每个请求中创建为新的实例。

举个例子,我想创建一个带有RequestId字段的日志记录器,并将该日志记录器传递给所有解析器和较低级别的逻辑,以使用相同的日志记录器。

非常感谢您对此的任何见解!谢谢!

英文:

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 !

答案1

得分: 1

每次请求重新创建根解析器并不是一个好主意。代码注释中提到的依赖注入是指服务范围的依赖项,这些是你需要在根解析器中设置一次的东西,例如配置值、缓存等。

如果你需要请求范围的依赖项,请使用gqlgen中的中间件和Server类型上可用的Around*方法。这些中间件函数的第一个参数是context.Context,你可以在其中设置请求范围的值。

gqlgen的源代码中包含了一个代码注释块,解释了请求生命周期的工作方式(作者:Brandon Sprague):

//  +--- REQUEST   POST /graphql --------------------------------------------+
//  | +- OPERATION query OpName { viewer { name } } -----------------------+ |
//  | |  RESPONSE  { "data": { "viewer": { "name": "bob" } } }             | |
//  | +- OPERATION subscription OpName2 { chat { message } } --------------+ |
//  | |  RESPONSE  { "data": { "chat": { "message": "hello" } } }          | |
//  | |  RESPONSE  { "data": { "chat": { "message": "byee" } } }           | |
//  | +--------------------------------------------------------------------+ |
//  +------------------------------------------------------------------------+
  • AroundOperations 在每个querymutationsubscription操作之前调用。
  • AroundResponses 在返回响应之前调用。
  • AroundRootFields 在进入每个根解析器之前调用。在上面的查询示例中,它在viewer解析器之前调用。
  • AroundFields 在每个字段解析器之前调用。

对于日志记录器,你可以使用AroundOperations。你可以在中间件本身中计算所需的任何值,或者使用在封闭作用域中初始化的值。

logger := // 创建一个日志记录器

gqlSrv = handler.NewDefaultServer(graphSchema)

gqlSrv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
		ctx = context.WithValue(ctx, "request-id-key", "1234")
        ctx = context.WithValue(ctx, "logger-key", logger)
        // 调用next来执行链中的下一个中间件或解析器
        next(ctx)
	})

然后在你的解析器实现中,你可以像往常一样从上下文中访问这些值:ctx.Value("logger-key")。(注意,与其使用字符串,最好使用未导出的结构体作为上下文键)。

英文:

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 the Server type. These middleware functions get a context.Context as 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):

>
> // +--- REQUEST POST /graphql --------------------------------------------+
> // | +- OPERATION query OpName { viewer { name } } -----------------------+ |
> // | | RESPONSE { "data": { "viewer": { "name": "bob" } } } | |
> // | +- OPERATION subscription OpName2 { chat { message } } --------------+ |
> // | | RESPONSE { "data": { "chat": { "message": "hello" } } } | |
> // | | RESPONSE { "data": { "chat": { "message": "byee" } } } | |
> // | +--------------------------------------------------------------------+ |
> // +------------------------------------------------------------------------+
>

  • AroundOperations is called before each query, mutation or subscription operation.
  • AroundResponses is called before returning the response
  • AroundRootFields is called before entering each root resolver. In the query example above, it's called before the viewer resolver.
  • AroundFields is 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.

logger := // create some logger

gqlSrv = handler.NewDefaultServer(graphSchema)

gqlSrv.AroundOperations(func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler {
		ctx = context.WithValue(ctx, "request-id-key", "1234")
        ctx = context.WithValue(ctx, "logger-key", logger)
        // call next to execute the next middleware or resolver in the chain
        next(ctx)
	})

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

huangapple
  • 本文由 发表于 2023年5月13日 15:10:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76241498.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定