Skip to content
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

[Feature]Support ServiceRegister ServerOption #1447

Closed
letian0805 opened this issue Sep 7, 2021 · 15 comments
Closed

[Feature]Support ServiceRegister ServerOption #1447

letian0805 opened this issue Sep 7, 2021 · 15 comments
Labels

Comments

@letian0805
Copy link
Contributor

letian0805 commented Sep 7, 2021

What problem is the feature used to solve?

当前的初始化顺序是:先创建Service,然后创建Server。创建Server的模块中引入所有Service模块,并将Server作为参数调用Service的Register函数向Server注册。比如:

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, greeter1 *service.Greeter1Service, greeter2 *service.Greeter2Service) *grpc.Server {
	var opts = []grpc.ServerOption{
		grpc.Middleware(
			recovery.Recovery(),
			tracing.Server(),
			logging.Server(log.DefaultLogger),
			metrics.Server(),
			validate.Validator(),
		),
	}
	srv := grpc.NewServer(opts...)
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

可以考虑由Server提供ServiceRegister选项,各Service提供Register注入到Server中,将Server依赖Service变为Service依赖Server。比如:
server/grpc.go

import "mydemo/internal/service"

func NewGRPCServer(c *conf.Server) *grpc.Server {
	var opts = []grpc.ServerOption{
                grpc.ServiceRegisters(service.Registers...),
		grpc.Middleware(
			recovery.Recovery(),
			tracing.Server(),
			logging.Server(log.DefaultLogger),
			metrics.Server(),
			validate.Validator(),
		),
	}
	srv := grpc.NewServer(opts...)
	return srv
}

这样代码会简洁很多
而register的实现可以放到各个Service模块内。比如:
service/service.go

package "service"
var Registers []grpc.ServiceRegister

service/greeter_service.go

package "service"

func init(){
	greeter := NewGreeterService()
	register := grpc.RegisterFunc(func(srv *grpc.Server){
		v1.RegisterGreeterService(srv, greeter)
	})
	Registers = append(Registers, register)
}

Requirements description of the feature

References

@iamharvey
Copy link
Sponsor Contributor

iamharvey commented Sep 9, 2021

目前的方式有它的好处:可以在启动程序代码中一览注册的服务,这样代码可读性更好。

@letian0805
Copy link
Contributor Author

目前的方式有它的好处:可以在启动程序代码中一览注册的服务,这样代码可读性更好。

这两个不冲突。用户可以根据自己业务特点选择目前的方式还是选择 ServiceRegister

@fifsky
Copy link
Contributor

fifsky commented Sep 9, 2021

package "service"

func init(){
	greeter := NewGreeterService()
	register := grpc.RegisterFunc(func(srv *grpc.Server){
		v1.RegisterGreeterService(srv, greeter)
	})
	Registers = append(Registers, register)
}

这样做的话NewGreeterService如果本身有依赖,就没有办法注入

@letian0805
Copy link
Contributor Author

package "service"

func init(){
	greeter := NewGreeterService()
	register := grpc.RegisterFunc(func(srv *grpc.Server){
		v1.RegisterGreeterService(srv, greeter)
	})
	Registers = append(Registers, register)
}

这样做的话NewGreeterService如果本身有依赖,就没有办法注入

我这里只是举个例子。不一定写在 init 里,也可以实现为 Init(xxx),然后放到wire的ProviderSet里

@tonybase
Copy link
Member

tonybase commented Sep 9, 2021

我比较赞成 harvey 的观点,这个方式感觉没有太大必要性,统一一种实现方式尽量地简单使用。

@tonybase tonybase closed this as completed Sep 9, 2021
@FirYuen
Copy link

FirYuen commented Nov 1, 2021

目前的方式有它的好处:可以在启动程序代码中一览注册的服务,这样代码可读性更好。

你好,新人问个问题, 目前的这种方式是需要注册一个service就在NewGRPCServer中传入一个,然后手动注册service,这样如果service多了那NewGRPCServer不就需要传入很多service参数吗?

@iamharvey
Copy link
Sponsor Contributor

目前的方式有它的好处:可以在启动程序代码中一览注册的服务,这样代码可读性更好。

你好,新人问个问题, 目前的这种方式是需要注册一个service就在NewGRPCServer中传入一个,然后手动注册service,这样如果service多了那NewGRPCServer不就需要传入很多service参数吗?

什么叫传入多个service参数,不是很理解。

另外,你说的情况是否还涉及到另一个问题,一个GRPC server中应该包含多少个所谓的service? 这里涉及微服务封装原则的问题,可能又是另一个话题了。

@FirYuen
Copy link

FirYuen commented Nov 1, 2021

感谢回复,我的疑问是这样的,在如下的代码中NewGRPCServer中传入了greeter,greeter1,greeter2三个参数,这种场景下传三个参数代码就很长了,那么如果有10个service,岂不是要传10个参数进来?

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, greeter1 *service.Greeter1Service, greeter2 *service.Greeter2Service) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

所以对于这样的场景,您的建议是将每个service都做成一个服务,
/proj/app/service1/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

/proj/app/service2/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService1) *grpc.Server {
       ........
   v1.RegisterGreeter1Server(srv, greeter1)
   return srv
}

/proj/app/service3/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService2) *grpc.Server {
        ........
	v1.RegisterGreeter1Server(srv, greeter2)
	return srv
}

这样去拆分项目吗

@FirYuen
Copy link

FirYuen commented Nov 1, 2021

目前的方式有它的好处:可以在启动程序代码中一览注册的服务,这样代码可读性更好。

你好,新人问个问题, 目前的这种方式是需要注册一个service就在NewGRPCServer中传入一个,然后手动注册service,这样如果service多了那NewGRPCServer不就需要传入很多service参数吗?

什么叫传入多个service参数,不是很理解。

另外,你说的情况是否还涉及到另一个问题,一个GRPC server中应该包含多少个所谓的service? 这里涉及微服务封装原则的问题,可能又是另一个话题了。

回复在上面,忘了引用了

@letian0805
Copy link
Contributor Author

对于一个业务比较复杂的服务来说,会存在多个service注册到一个grpc.Server。说实在的,wire 这种从函数参数注入依赖的做法对于有代码洁癖的人来说,感觉很不好。每加一个service,除了参数多了外,还需要重新执行wire命令生成新的注入代码。

@iamharvey
Copy link
Sponsor Contributor

感谢回复,我的疑问是这样的,在如下的代码中NewGRPCServer中传入了greeter,greeter1,greeter2三个参数,这种场景下传三个参数代码就很长了,那么如果有10个service,岂不是要传10个参数进来?

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, greeter1 *service.Greeter1Service, greeter2 *service.Greeter2Service) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

所以对于这样的场景,您的建议是将每个service都做成一个服务, /proj/app/service1/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

/proj/app/service2/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService1) *grpc.Server {
       ........
   v1.RegisterGreeter1Server(srv, greeter1)
   return srv
}

/proj/app/service3/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService2) *grpc.Server {
        ........
	v1.RegisterGreeter1Server(srv, greeter2)
	return srv
}

这样去拆分项目吗

1)如果是我,应该也就是这样老老实实去注入参数申明。当然也可以考虑传入一个方法类型的参数,something like:

func NewGRPCServer(c *conf.Server,  regFunc util.ServiceRegistration) *grpc.Server {
         ........
 	regFunc(srv)
        return srv
}

当然,这种方式的问题是:我无法直观的了解到注册了哪些服务,可能需要进入另一个文件来查看。

2)如果可以拆分,那拆分自然是一种优雅和办法;

3)也可以通过配置文件来配置哪些服务需要在启动时被注册进来(这种方式运维友好);

在我看来,选择哪一种我觉得都是OK的,这取决于使用框架的人。但如果让注册服务变成一种非显性(参数注入式)的,特定的模式,并且要求使用者必须遵循,那就不妥了。框架只需要保证简单且debug友好即可。

@FirYuen
Copy link

FirYuen commented Nov 1, 2021

感谢回复,我的疑问是这样的,在如下的代码中NewGRPCServer中传入了greeter,greeter1,greeter2三个参数,这种场景下传三个参数代码就很长了,那么如果有10个service,岂不是要传10个参数进来?

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, greeter1 *service.Greeter1Service, greeter2 *service.Greeter2Service) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

所以对于这样的场景,您的建议是将每个service都做成一个服务, /proj/app/service1/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

/proj/app/service2/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService1) *grpc.Server {
       ........
   v1.RegisterGreeter1Server(srv, greeter1)
   return srv
}

/proj/app/service3/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService2) *grpc.Server {
        ........
	v1.RegisterGreeter1Server(srv, greeter2)
	return srv
}

这样去拆分项目吗

1)如果是我,应该也就是这样老老实实去注入参数申明。当然也可以考虑传入一个方法类型的参数,something like:

func NewGRPCServer(c *conf.Server,  regFunc util.ServiceRegistration) *grpc.Server {
         ........
 	regFunc(srv)
        return srv
}

当然,这种方式的问题是:我无法直观的了解到注册了哪些服务,可能需要进入另一个文件来查看。

2)如果可以拆分,那拆分自然是一种优雅和办法;

3)也可以通过配置文件来配置哪些服务需要在启动时被注册进来(这种方式运维友好);

在我看来,选择哪一种我觉得都是OK的,这取决于使用框架的人。但如果让注册服务变成一种非显性(参数注入式)的,特定的模式,并且要求使用者必须遵循,那就不妥了。框架只需要保证简单且debug友好即可。

感谢回复,收获很大!

@letian0805
Copy link
Contributor Author

感谢回复,我的疑问是这样的,在如下的代码中NewGRPCServer中传入了greeter,greeter1,greeter2三个参数,这种场景下传三个参数代码就很长了,那么如果有10个service,岂不是要传10个参数进来?

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService, greeter1 *service.Greeter1Service, greeter2 *service.Greeter2Service) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

所以对于这样的场景,您的建议是将每个service都做成一个服务, /proj/app/service1/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService) *grpc.Server {
        ........
	v1.RegisterGreeterServer(srv, greeter)
	v1.RegisterGreeter1Server(srv, greeter1)
	v1.RegisterGreeter2Server(srv, greeter2)
	return srv
}

/proj/app/service2/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService1) *grpc.Server {
       ........
   v1.RegisterGreeter1Server(srv, greeter1)
   return srv
}

/proj/app/service3/server/internal/server/grpc.go

func NewGRPCServer(c *conf.Server, greeter *service.GreeterService2) *grpc.Server {
        ........
	v1.RegisterGreeter1Server(srv, greeter2)
	return srv
}

这样去拆分项目吗

1)如果是我,应该也就是这样老老实实去注入参数申明。当然也可以考虑传入一个方法类型的参数,something like:

func NewGRPCServer(c *conf.Server,  regFunc util.ServiceRegistration) *grpc.Server {
         ........
 	regFunc(srv)
        return srv
}

当然,这种方式的问题是:我无法直观的了解到注册了哪些服务,可能需要进入另一个文件来查看。

2)如果可以拆分,那拆分自然是一种优雅和办法;

3)也可以通过配置文件来配置哪些服务需要在启动时被注册进来(这种方式运维友好);

在我看来,选择哪一种我觉得都是OK的,这取决于使用框架的人。但如果让注册服务变成一种非显性(参数注入式)的,特定的模式,并且要求使用者必须遵循,那就不妥了。框架只需要保证简单且debug友好即可。

wire在使用过程中也有很多坑:
1.改完代码后需要重新执行wire,如果忘了执行,就可能导致新加的service没有注入
2.即使记得执行wire,生成代码的过程中也容易出现各种代码问题,需要了解wire的机制去解决
3.随着依赖关系越来越复杂,可能会导致函数参数越来越多,代码变得不整洁

debug的话,在关键的地方加好日志即可,而且这个日志是必不可少的。另外,约定大于配置,框架更重要的是制定易于使用的规范(约定)。NewGRPCServer函数中的代码更像是配置,不是约定。ServiceRegister是约定,每个service都要提供自己的 Register,Server不关心具体的Service,只关心 Register

@FirYuen
Copy link

FirYuen commented Nov 1, 2021

wire在使用过程中也有很多坑:
1.改完代码后需要重新执行wire,如果忘了执行,就可能导致新加的service没有注入
2.即使记得执行wire,生成代码的过程中也容易出现各种代码问题,需要了解wire的机制去解决
3.随着依赖关系越来越复杂,可能会导致函数参数越来越多,代码变得不整洁

感谢回复!
是的,之前没有接触过依赖注入这种概念,用起来确实不太舒服,依赖注入执行了很多操作,不去看他生成的代码可能无法了解其中的逻辑
关于添加service后每次需要执行wire的问题,收到提醒后我正在尝试使用air来解决

@letian0805
Copy link
Contributor Author

wire在使用过程中也有很多坑:
1.改完代码后需要重新执行wire,如果忘了执行,就可能导致新加的service没有注入
2.即使记得执行wire,生成代码的过程中也容易出现各种代码问题,需要了解wire的机制去解决
3.随着依赖关系越来越复杂,可能会导致函数参数越来越多,代码变得不整洁

感谢回复! 是的,之前没有接触过依赖注入这种概念,用起来确实不太舒服,依赖注入执行了很多操作,不去看他生成的代码可能无法了解其中的逻辑 关于添加service后每次需要执行wire的问题,收到提醒后我正在尝试使用air来解决

我的做法是不用wire。我自己实现了一套组件管理器,通过配置驱动的方式来控制组件的初始化和依赖管理。规范好配置格式,按照约定组织配置文件即可,看配置文件就知道哪个组件没有注入,比wire更方便更简洁。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants