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”。换句话说,借助golang import本身后序遍历的顺序来保存包的初始化顺序(越底层的包越先初始化),这样在main import 完毕后,我们获得了整个项目的上下游依赖顺序。然后,我们显式地加载配置文件;然后,我们再依据拿到的依赖顺序,显式地初始化项目中各个package的依赖:

对于wire, 我们把每个package都认为是一个服务Service。Service 内部只维护自身的状态, 直接调用自身的依赖,不需要关注依赖本身的具体初始化方法,初始化时机。只需要认为依赖模块已经准备好了

要保证按照依赖的方式初始化模块,每个模块只需要在自身模块的init方法中通过wire.Append() 方法把自己添加到 wire 中。但只是添加到wire中,而并未初始化

golang 在执行main时,根据 import 自动调用依赖模块的 init 的方法, 所以main 模块 init 执行结束之后, wire 就自动拥有了整个项目的 【按照后序遍历顺序排列】依赖列表

最后,在main方法中,读取外部的配置文件之后,再调用wire.Init() 就可以顺利地、依次地按照依赖顺序调用每个模块的Service.Init()方法,从而顺利启动整个服务

你可能感兴趣的:(golang依赖注入-基于图的后序遍历 2022-04-21)