golang依赖注入-基于图的后序遍历 2022-04-21
google wire
go官方提供了一个wire模块https://github.com/google/wire来完成依赖注入
wire 定义了两个概念:provider 和 injector
- Provider。Provider 往往是一些简单的工厂函数,如:
func NewPerson() *Person {
...
}
func NewStudent(p Person) *Student{
...
}
func NewCollegeStudent(s Student) *CollegeStudent {
...
}
这里Student实例依赖Person实例,CollegeStudent实例依赖Student实例
- Injector。Injectors are generated functions that call providers in dependency order. You write the injector’s signature, including any needed inputs as arguments, and insert a call to wire.Build with the list of providers or provider sets that are needed to construct the end result. Injector其实还是个工厂函数,复杂的点在于函数内部需要按照依赖关系编排代码,从而保证最后能够正确返回目标实例
这个injector原本是程序员手敲的,如:
func GetCollegeStudent() *CollegeStudent {
p := NewPerson()
s := NewStudent(p)
cs := NewCollegeStudent(s)
return cs
}
wire的作用就是,把一系列的依赖顺序给隐藏了,你只需把所有的provider传入wire.Build,无需考虑provider的顺序
func GetCollegeStudent() *CollegeStudent {
panic(wire.Build(
NewCollegeStudent,
NewStudent,
NewPerson,
))
}
然后跑一下go generate
的命令生成新的代码
go generate will create a new file with the generated code
生成的代码就和手敲版很相似了
因此,google的wire实际上就是个代码生成工具,用来解决依赖问题的代码生成工具
self-develop wire
本文的重点是,自研的基于图的后序遍历的依赖注入框架:wire
wire——延迟package的初始化
一般而言,一个golang项目(或者说一个module)的main.go会import项目内(或者说module内)的其他package,也就是main.go会对其他package产生依赖,而这些其他的package又可能import更多其他的package,从而形成了比较复杂的引用关系图
依赖的抽象:后序遍历 与 前序遍历
上层能力依赖下层能力的初始化,因此最底层的依赖应该最先初始化,初始化的时候应该遵循图的后序遍历;在进程出现问题的时候又应该最后一个停止,停止的时候遵循图的前序遍历
go会根据 import
自动调用依赖模块的 init
的方法,最底层的package最先被import,这就是后序遍历。利用这个特性,我们可以在每个要被import的package自身的init方法中直接完成包自身的初始化
但是,项目里经常存在一个config包,通常是最底层的依赖包。它的初始化必须依赖于main.go读入配置文件路径,而按上面的设想,main.go又依赖于config的初始化,两者相互依赖对方的初始化,死锁,矛盾。因此,当项目内的部分包的初始化依赖于外部参数时,init方法完成初始化就行不通
那么,我们可以延迟每个package的初始化,在保证各种外部参数都准备好之后再行init
对于
wire
, 我们把每个package都认为是一个服务Service
。Service 内部只维护自身的状态, 直接调用自身的依赖,不需要关注依赖本身的具体初始化方法,初始化时机。只需要认为依赖模块已经准备好了
要保证按照依赖的方式初始化模块,每个模块只需要在自身模块的init方法中通过
wire.Append()
方法把自己添加到wire
中。但只是添加到wire中,而并未初始化
golang 在执行main时,根据
import
自动调用依赖模块的init
的方法, 所以main
模块init
执行结束之后,wire
就自动拥有了整个项目的 【按照后序遍历顺序排列】依赖列表
最后,在main方法中,读取外部的配置文件之后,再调用
wire.Init()
就可以顺利地、依次地按照依赖顺序调用每个模块的Service.Init()
方法,从而顺利启动整个服务