路人战队|Facebook 在 Golang 依赖注入的实现

依赖注入是一个经典的设计模式 , 在解决复杂的对象依赖关系方面是一个非常行之有效的手段 。
对于有反射功能的语言来说 , 实现依赖注入都比较方便一些 。 在 Golang 中有几个比较知名的依赖注入开源库 , 例如 google/wireuber-go/dig 以及 facebookgo/inject 等 。
本文将基于 facebookgo/inject 介绍依赖注入, 接下来将会着重讨论以下几点内容:

  1. 依赖注入的背景以及解决的问题
  2. facebookgo/inject 的使用方法
  3. facebookgo/inject 的缺陷
依赖注入的背景对于稍微复杂些的项目 , 我们往往就会遇到对象之间复杂的依赖关系 。 手动管理和初始化这些管理关系将会极其繁琐 , 依赖注入可以帮我们自动实现依赖的管理和对象属性的赋值 , 将我们从这些繁琐的依赖管理中解放出来 。
以一个常见的 HTTP 服务为例 , 我们在开发后台时往往会把代码分为 Controller、Service 等层次 。 如下:
type UserController struct {UserService *UserServiceConf*Conf}type PostController struct {UserService *UserServicePostService *PostServiceConf*Conf}type UserService struct {Db*DBConf *Conf}type PostService struct {Db *DB}type Server struct {UserApi *UserControllerPostApi *PostController}上述的代码例子中 , 有两个 Controller:UserController 和 PostController , 分别用来接收用户和文章的相关请求逻辑 。 除此之外还会有 Service 相关类、Conf 配置文件、DB 连接等 。
这些对象之间存在比较复杂的依赖关系 , 这就给项目的初始化带来了一些困扰 。 对于以上代码 , 对应初始化逻辑大概就会是这样:
func main() {conf := loadConf()db := connectDB()userService :=err != nil {panic(err)}if err := graph.Populate(); err != nil {panic(err)}server.Run()}
  1. 首先每一个需要注入的字段都需要打上 inject:"" 这样的 tag 。 所谓依赖注入 , 这里的依赖指的就是对象中包含的字段 , 而注入则是指有其它程序会帮你对这些字段进行赋值 。
  2. 其次 , 我们使用 inject.Graph{} 创建一个 graph 对象 。 这个 graph 对象将负责管理和注入所有的对象 。 至于为什么叫 Graph , 其实这个名词起的非常形象 , 因为各个对象之间的依赖关系 , 也确实像是一张图一样 。
  3. 接下来 , 我们使用 graph.Provide() 将需要注入的对象提供给 graph。 graph.Provide(
  4. 最后调用 Populate 函数 , 开始进行注入 。
从代码中可以看到 , 我们一共就向 Graph 中 Provide 了三个对象 。 我们提供了 server 对象 , 是因为它是一个顶层对象 。提供了 conf 和 db 对象 , 是因为所有的对象都依赖于它们 , 可以说它们是基础对象了 。
但是其他的对象呢? 例如 UserApi 和 UserService 呢?我们并没有向 graph 调用 Provide 过 。 那么它们是怎么完成赋值和注入的呢?
其实从下面这张对象依赖图能够很简单的看清楚 。
路人战队|Facebook 在 Golang 依赖注入的实现从这个依赖图中可以看出 ,conf 和 db 对象是属于根节点 , 所有的对象都依赖和包含着它们 。 而 server 属于叶子节点 , 不会有其他对象依赖它了 。
我们需要提供给 Graph 的就是根节点和叶子节点 , 对于中间节点来说 , Graph 会通过 inject:"" 标签 , 自动将其 Provide 到 Graph 中 , 并进行注入 。
对以上例子 , 我们深入剖析下 Graph 内部进行 Populate 时都发生了哪些动作:
  1. Graph 首先解析 server 对象 , 发现其有两个标记为 inject 的字段: UserApi 和 PostApi。 其类型 UserController 和 PostController , Graph 中从未出现过这两个类型 。 因此 , Graph 会自动对该字段调用 Provide , 提供给 Graph 。


    推荐阅读