从Kratos中开始学习Go的依赖注入

531 阅读3分钟

依赖注入(DI):dependency injection

前言

首先从java Spring中的依赖注入开始理解,在全局初始化一个工具,然后其他地方需要用的时候直接调用已经初始化的工具直接使用,减低反复创建对象和反复写代码。(先不谈IoCAoP

Java里面直接加上注解就能够使用,希望google的wire也能完成。

以下下是基于Kratos中的Demo项目进行分析,不适合的请左拐,另外本文只是个人笔记,难免有错,大佬请使劲喷。

Wire

github地址:github.com/google/wire

与其他依赖注入工具不同,比如 uber 的 dig 和 facebook 的 inject,这 2 个工具都是使用反射实现的依赖注入,而且是运行时注入(runtime dependency injection)。

wire 是编译代码生成代码的依赖注入,是编译期间注入依赖代码(compile-time dependency injection)。而且代码生成期间,如果依赖注入有问题,生成依赖代码时就会出错,就可以报出问题来,而不必等到代码运行时才暴露出问题。这也意味着我们新建对象或者接口的时候需要重新把代码生成注入到项目中去。

Provider:提供一个对象。

Injector:负责根据对象依赖关系,生成新程序

Kratos介绍的wire

Provider

Provider 是一个普通的 Go Func ,这个方法也可以接收其它 Provider ****的返回值,从而形成了依赖注入;

我们观察下Kratos项目中cmd/xxx/wire.go代码

// wireApp init kratos application.
func wireApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) {
	// 分别进行依赖注入
	panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))
}

很简单里面就一个函数,分别把serverdatabizservice定义一系列对象生成,提供给其他对象使用。

继续往下,我们进入service.ProviderSet代码,里面也就一行,service就是被我们实现的application层,一般不进行复杂业务转换。

// ProviderSet is service providers. 在wire.go 里面被调用
var ProviderSet = wire.NewSet(NewGreeterService)

我们在GreeterService里面添加一行输出,可以观察到它们在项目一启动他们就被创建了。

// NewGreeterService new a greeter service.
func NewGreeterService(uc *biz.GreeterUsecase) *GreeterService {
    fmt.Println("进入greeter Service")
    return &GreeterService{uc: uc}
}
2023/08/04 10:20:12 maxprocs: Leaving GOMAXPROCS=8: CPU quota undefined
DEBUG msg=config loaded: config.yaml format: yaml
进入greeter Service
INFO ts=2023-08-04T10:20:12+08:00 caller=http/server.go:302 service.id=Ymir.local service.name= service.version= trace.id= span.id= msg=[HTTP] server listening on: [::]:8000
INFO ts=2023-08-04T10:20:12+08:00 caller=grpc/server.go:205 service.id=Ymir.local service.name= service.version= trace.id= span.id= msg=[gRPC] server listening on: [::]:9000

Injector

Provider已经提供了对象,我们应该如何注入呢?我们看看cmd/xxx/wire_gen.go文件,显然这里和wire.go一样都是入口 这几个data、biz.....就是前面在wire.go传过来的对象,并且直接以代码的形式出现,这得益于wire命令。文章最开始说过wire编译代码生成代码的依赖注入,这点和我们在spring中用的不一样(起码和我用的不一样),有点类似lombok插件。

// Injectors from wire.go:

// wireApp init kratos application.
func wireApp(confServer *conf.Server, confData *conf.Data, logger log.Logger) (*kratos.App, func(), error) {
	dataData, cleanup, err := data.NewData(confData, logger)
	if err != nil {
		return nil, nil, err
	}
	greeterRepo := data.NewGreeterRepo(dataData, logger)
	greeterUsecase := biz.NewGreeterUsecase(greeterRepo, logger)
	greeterService := service.NewGreeterService(greeterUsecase)
	grpcServer := server.NewGRPCServer(confServer, greeterService, logger)
	httpServer := server.NewHTTPServerStudent(confServer, logger)
	app := newApp(logger, grpcServer, httpServer)
	return app, func() {
		cleanup()
	}, nil
}

所以我们在Kratos里面的使用顺序大概是,新建完protobuf文件生成后,绑定到wire的provider里面,那么怎么用呢?额,不知道,如何像spring那样随心所欲的注解注入,直接注入进来呢?晚上再补呗