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

injectorwire生成的函数,我们通过调用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的步骤描述如下:

  1. 确定所生成injector函数的函数签名:func initUserStore(info ConnectionInfo) (*UserStore, error)
  2. 感知返回值第一个参数是UserStore
  3. 检查wire.Build列表,找到UserStoreprovider:NewUserStore
  4. 由函数签名func NewUserStore(cfg *Config, db *mysql.DB)得知NewUserStore依赖于Config, 和mysql.DB
  5. 检查wire.Build列表,找到Configmysql.DBprovider:NewDefaultConfigNewDB
  6. 由函数签名func NewDefaultConfig() *Config得知Config没有其他依赖了。
  7. 由函数签名func NewDB(info *ConnectionInfo) (*mysql.DB, error)得知mysql.DB依赖于ConnectionInfo
  8. 检查wire.Build列表,找不到ConnectionInfoprovider,但在injector函数签名中发现匹配的入参类型,直接使用该参数作为NewDB的入参。
  9. 感知返回值第二个参数是error
  10. 按依赖关系,按顺序调用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接口,其中mockUserRepoUserRepository的一个实现,由于在Go的最佳实践中,更推荐返回具体实现而不是接口。所以mockUserRepoprovider函数返回的是*mockUserRepo这一具体类型。wire无法自动将具体实现与接口进行关联,我们需要显示声明它们之间的关联关系。通过wire.NewSetwire.Bind*mockUserRepoUserRepository进行绑定:
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
}

wireprovider的返回值个数和顺序有所规定:

  • 第一个参数是需要生成的依赖对象
  • 如果返回2个返回值,第二个参数必须是func()或者error
  • 如果返回3个返回值,第二个参数必须是func(),第三个参数则必须是error