Wire 等依赖注入工具旨在简化初始化代码的管理
💡 依赖注入 是一种标准技术,用于生成灵活且松散耦合的代码,通过显式地为组件提供它们工作所需的所有依赖项。
Wire 介绍#
参考文章:Golang依赖注入框架wire使用详解_魂影魔宅-CSDN博客_golang wire
Wire 有两个基本概念:提供者(provider)和注入器(Injector)。
官方的定义:
provider: a function that can produce a value. These functions are ordinary Go code.
injector: a function that calls providers in dependency order.
With Wire, you write the injector’s signature, then Wire generates the function’s body.
- 提供者(provider):一个可以产生值的函数(通常是构造函数)。这些函数都是普通的 Go 代码
- 注入器(Injector):一个按依赖顺序调用提供者的函数。
使用 Wire,您编写注入器(Injector)的签名,然后 Wire 生成函数的主体。
provider#
provider就是普通的Go函数,可以把它看作是某对象的构造函数,我们通过provider告诉wire该对象的依赖情况:
1
2
3
4
5
6
7
8
9
10
11
| // 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#
injector是wire生成的函数,我们通过调用injector来获取我们所需的对象或值,injector会按照依赖关系,按顺序调用provider函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // 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的呢?我们需要写一个函数来告诉它:
- 定义
injector的函数签名 - 在函数中使用
wire.Build方法列举生成injector所需的provider
1
2
3
4
5
6
7
| 例如:
// initUserStore用于声明injector的函数签名
func initUserStore(info ConnectionInfo) (*UserStore, error) {
// wire.Build声明要获取一个UserStore需要调用到哪些provider函数
wire.Build(UserStoreSet, NewDB)
return nil, nil // 这些返回值wire并不关心。
}
|
有了上面的函数,wire就可以得知如何生成injector了。wire生成injector的步骤描述如下:
- 确定所生成
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 - 按依赖关系,按顺序调用
provider函数,拼装injector函数。
接口绑定#
💡 根据依赖倒置原则(Dependence Inversion Principle),对象应当依赖于接口,而不是直接依赖于具体实现。
wire中如何处理接口依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| // UserService
type UserService struct {
userRepo UserRepository// <-- UserService依赖UserRepository接口
}
type User struct {
ID int
Name string
}
// UserRepository 存放User对象的数据仓库接口,比如可以是mysql,restful api.
type UserRepository interface {
// GetUserByID 根据ID获取User, 如果找不到User返回对应错误信息
GetUserByID(id int) (*User, error)
}
// NewUserService *UserService构造函数
func NewUserService(userRepo UserRepository) *UserService {
return &UserService{
userRepo:userRepo,
}
}
// mockUserRepo 模拟一个UserRepository实现
type mockUserRepo struct {
foo string
bar int
}
// GetUserByID UserRepository接口实现
func (u *mockUserRepo) GetUserByID(id int) (*User,error){
return &User{}, nil
}
// NewMockUserRepo *mockUserRepo构造函数
func NewMockUserRepo(foo string,bar int) *mockUserRepo {
return &mockUserRepo{
foo:foo,
bar:bar,
}
}
// MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))
|
- 在这个例子中,
UserService依赖UserRepository接口,其中mockUserRepo是UserRepository的一个实现,由于在Go的最佳实践中,更推荐返回具体实现而不是接口。所以mockUserRepo的provider函数返回的是*mockUserRepo这一具体类型。wire无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。通过wire.NewSet和wire.Bind将*mockUserRepo与UserRepository进行绑定:
1
2
| // MockUserRepoSet 将 *mockUserRepo与UserRepository绑定
var MockUserRepoSet = wire.NewSet(NewMockUserRepo,wire.Bind(new(UserRepository), new(*mockUserRepo)))
|
定义injector函数签名:
1
2
3
| func InitializeUserService(foo string, bar int) *UserService{
wire.Build(NewUserService,MockUserRepoSet)// 使用MockUserRepoSetreturn nil
}
|
wire对provider的返回值个数和顺序有所规定:
- 第一个参数是需要生成的依赖对象
- 如果返回2个返回值,第二个参数必须是
func()或者error - 如果返回3个返回值,第二个参数必须是
func(),第三个参数则必须是error