在第一篇文章中提到过,为了让代码可测,须要用依赖注入的方式来构建咱们的对象,而一般咱们会在main.go
作依赖注入,这就致使main.go
会愈来愈臃肿。为了让单元测试得以顺利进行,main.go
牺牲了它本应该纤细苗条的身材。太胖的main.go
可不是什么好的信号,本篇将介绍依赖注入框架(wire),致力于帮助main.go
恢复身材。mysql
在main.go
中作依赖注入,意味着在初始化代码中咱们要管理:git
对于小型项目而言,依赖的数量比较少,初始化代码不会不少,不须要引入依赖注入框架。但对于依赖较多的中大型项目,初始化代码又臭又长,可读性和维护性变的不好,随意感觉一下:程序员
func main() {
config := NewConfig()
// db依赖配置
db, err := ConnectDatabase(config)
if err != nil {
panic(err)
}
// PersonRepository 依赖db
personRepository := NewPersonRepository(db)
// PersonService 依赖配置 和 PersonRepository
personService := NewPersonService(config, personRepository)
// NewServer 依赖配置和PersonService
server := NewServer(config, personService)
server.Run()
}
复制代码
实践代表,修改有大量依赖关系的初始化代码是一项乏味且耗时的工做。这个时候,咱们就须要依赖注入框架来帮忙,简化初始化代码。github
上述代码来自:blog.drewolson.org/dependency-…golang
wire是google开源的依赖注入框架。或者引用官方的话来讲:“Wire is a code generation tool that automates connecting components using dependency injection”。sql
除了wire,Go的依赖注入框架还有Uber的dig和Facebook的inject,它们都是使用反射机制来实现运行时依赖注入(runtime dependency injection),而wire则是采用代码生成的方式来达到编译时依赖注入(compile-time dependency injection)。使用反射带来的性能损失却是其次,更重要的是反射使得代码难以追踪和调试(反射会令Ctrl+左键失效...)。而wire生成的代码是符合程序员常规使用习惯的代码,十分容易理解和调试。
关于wire的优势,在官方博文上有更详细的的介绍:blog.golang.org/wire框架
本部份内容参考官方博文:blog.golang.org/wireide
wire有两个基本的概念:provider和injector。函数
provider就是普通的Go函数,能够把它看做是某对象的构造函数,咱们经过provider告诉wire该对象的依赖状况:
// NewUserStore是*UserStore的provider,代表*UserStore依赖于*Config和 *mysql.DB.
func NewUserStore(cfg *Config, db *mysql.DB) (*UserStore, error) {...}
// NewDefaultConfig是*Config的provider,没有依赖
func NewDefaultConfig() *Config {...}
// NewDB是*mysql.DB的provider,依赖于ConnectionInfo
func NewDB(info ConnectionInfo) (*mysql.DB, error) {...}
// UserStoreSet 可选项,可使用wire.NewSet将一般会一块儿使用的依赖组合起来。
var UserStoreSet = wire.NewSet(NewUserStore, NewDefaultConfig)
复制代码
injector是wire生成的函数,咱们经过调用injector来获取咱们所需的对象或值,injector会按照依赖关系,按顺序调用provider函数:
// File: wire_gen.go
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
// initUserStore是由wire生成的injector
func initUserStore(info ConnectionInfo) (*UserStore, error) {
// *Config的provider函数
defaultConfig := NewDefaultConfig()
// *mysql.DB的provider函数
db, err := NewDB(info)
if err != nil {
return nil, err
}
// *UserStore的provider函数
userStore, err := NewUserStore(defaultConfig, db)
if err != nil {
return nil, err
}
return userStore, nil
}
复制代码
injector帮咱们把按顺序初始化依赖的步骤给作了,咱们在main.go
中只须要调用initUserStore
方法就能获得咱们想要的对象了。
那么wire是怎么知道如何生成injector的呢?咱们须要写一个函数来告诉它:
wire.Build
方法列举生成injector所需的provider例如:
// initUserStore用于声明injector的函数签名
func initUserStore(info ConnectionInfo) (*UserStore, error) {
// wire.Build声明要获取一个UserStore须要调用到哪些provider函数
wire.Build(UserStoreSet, NewDB)
return nil, nil // 这些返回值wire并不关心。
}
复制代码
有了上面的函数,wire就能够得知如何生成injector了。wire生成injector的步骤描述以下:
func initUserStore(info ConnectionInfo) (*UserStore, error)
*UserStore
wire.Build
列表,找到*UserStore
的provider:NewUserStore
func NewUserStore(cfg *Config, db *mysql.DB)
得知NewUserStore
依赖于*Config
, 和*mysql.DB
wire.Build
列表,找到*Config
和*mysql.DB
的provider:NewDefaultConfig
和NewDB
func NewDefaultConfig() *Config
得知*Config
没有其余依赖了。func NewDB(info *ConnectionInfo) (*mysql.DB, error)
得知*mysql.DB
依赖于ConnectionInfo
。wire.Build
列表,找不到ConnectionInfo
的provider,但在injector函数签名中发现匹配的入参类型,直接使用该参数做为NewDB
的入参。error
栗子传送门:wire-examples
截止本文发布前,官方代表wire的项目状态是alpha,还不适合到生产环境,API存在变化的可能。
虽然是alpha,但其主要做用是为咱们生成依赖注入代码,其生成的代码十分通俗易懂,在作好版本控制的前提下,即便是API发生变化,也不会对生成环境形成多坏的影响。我认为仍是能够放心使用的。
本篇是本系列的最后一篇,回顾前几篇文章,咱们以单元测试的原理与基本思想为基础,介绍了表格驱动测试方法,gomock,testify,wire这几样实用工具,经历了“能写单元测试”到“写好单元测试”不断优化的过程。但愿本系列文章能让你有所收获。