1. 前言

1.1 什么是依赖注入?

configlog数据库客户端

在main.go中做依赖注入,意味着在初始化代码中我们要管理:

  1. 依赖的初始化顺序
  2. 依赖之间的关系
    对于小型项目而言,依赖的数量比较少,初始化代码不会很多,不需要引入依赖注入框架。但对于依赖较多的中大型项目,初始化代码又臭又长,可读性和维护性变的很差,如下一段启动函数:
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()
}

1.2 为什么要做使用依赖注入框架?

随着开发合作的同学的增多以及部门的要求增加,项目启动时的依赖越来越多,依赖之间还有先后顺序,有一些甚至是隐式的顺序,到时main函数的代码膨胀的非常迅速并且慢慢的变的不可维护了,这种情况下引入依赖注入框架其实可以省心很多。

1.3 依赖注入框架分类

Golang 的依赖注入框架有两类

使用 dig 功能会强大一些,它们都是使用反射机制来实现运行时依赖注入(runtime dependency injection), 但是缺点就是错误只能在运行时才能发现,这样如果不小心的话可能会导致一些隐藏的 bug 出现。

wire则是采用代码生成的方式来达到编译时依赖注入(compile-time dependency injection), 使用 wire 的缺点就是功能限制多一些,但是好处就是编译的时候就可以发现问题,并且生成的代码其实和我们自己手写相关代码差不太多,更符合直觉,心智负担更小,十分容易理解和调试。所以更加推荐 wire.

2. wire

2.0 安装

$GOPATH/bin
go get github.com/google/wire/cmd/wire

2.1 如何工作?

providerinjector

2.2 provider

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)

不过需要注意的是在wire 中不能存在两个 provider 返回相同的组件类型

2.3 Injector

injectorinjectorinjectorprovider
// 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
}
//+build wireinjectwire .wire_gen.go//+build !wireinject
main.goinitUserStore
wire.go
wire.Build
initUserStore
// initUserStore用于声明injector的函数签名
func initUserStore(info ConnectionInfo) (*UserStore, error) {  
    
    // wire.Build声明要获取一个UserStore需要调用到哪些provider函数
    wire.Build(UserStoreSet, NewDB)
    
    // 这些返回值wire并不关心
    return nil, nil 
}

有了上面的函数,wire就可以得知如何生成injector了。wire生成injector的步骤描述如下:

func initUserStore(info ConnectionInfo) (*UserStore, error)

2.4 一个完整的 🌰

provider.go
// provider.go

package example

// repo

// IPostRepo IPostRepo
type IPostRepo interface{}

// NewPostRepo NewPostRepo
func NewPostRepo() IPostRepo {
	return new(IPostRepo)
}

// usecase

// IPostUsecase IPostUsecase
type IPostUsecase interface{}
type postUsecase struct {
	repo IPostRepo
}

// NewPostUsecase NewPostUsecase
func NewPostUsecase(repo IPostRepo) IPostUsecase {
	return postUsecase{repo: repo}
}

// service service

// PostService PostService
type PostService struct {
	usecase IPostUsecase
}

// NewPostService NewPostService
func NewPostService(u IPostUsecase) *PostService {
	return &PostService{usecase: u}
}
NewPostServiceNewPostUsecasewire.go
wire.go
//+build wireinject

package example

import "github.com/google/wire"

func GetPostService() *PostService {
	
	panic(wire.Build(
		NewPostService,
		NewPostUsecase,
		NewPostRepo,
	))
}
wire.gowire .
// Code generated by Wire. DO NOT EDIT.

//go:generate wire
//+build !wireinject

package example

// Injectors from wire.go:

func GetPostService() *PostService {
	iPostRepo := NewPostRepo()
	iPostUsecase := NewPostUsecase(iPostRepo)
	postService := NewPostService(iPostUsecase)
	
	return postService
}

2.5 错误机制

缺少provider

wire .Provider

还是上面的例子,我们删除掉一个Provider函数试试

func GetPostService() *PostService {
	panic(wire.Build(
		NewPostService,
		NewPostUsecase,
	))
}
wire
▶ wire .
wire: /Go-000/Week04/blog/03_wire/01_example/wire.go:7:1: inject GetPostService: no provider found for github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.IPostRepo
        needed by github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.IPostUsecase in provider "NewPostUsecase" (/Go-000/Week04/blog/03_wire/01_example/example.go:22:6)
        needed by *github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.PostService in provider "NewPostService" (/Go-000/Week04/blog/03_wire/01_example/example.go:34:6)
wire: github.com/mohuishou/go-training/Week04/blog/03_wire/01_example: generate failed
wire: at least one generate failure

2.6 返回错误

goerrorwireinjectorerrorNewPostServiceerrorGetPostServiceInjector
// example.go
// NewPostService NewPostService
func NewPostService(u IPostUsecase) (*PostService, error) {
	return &PostService{usecase: u}, nil
}

// wire.go

func GetPostService() (*PostService, error) {
	panic(wire.Build(
		NewPostService,
		NewPostUsecase,
		NewPostRepo,
	))
}
if err
// wire_gen.go

func GetPostService() (*PostService, error) {
	
	iPostRepo := NewPostRepo()
	iPostUsecase := NewPostUsecase(iPostRepo)
	postService, err := NewPostService(iPostUsecase)
	
	if err != nil {
		return nil, err
	}
	
	return postService, nil
}

2.7 清理函数

providerwire

还是之前的示例,我们修改一下 NewPostRepo NewPostUsecase 让他们返回一个清理函数


// example.go

// NewPostRepo NewPostRepo
func NewPostRepo() (IPostRepo, func(), error) {
	return new(IPostRepo), nil, nil
}

// NewPostUsecase NewPostUsecase
func NewPostUsecase(repo IPostRepo) (IPostUsecase, func(), error) {
	return postUsecase{repo: repo}, nil, nil
}

// wire.go

func GetPostService() (*PostService, func(), error) {
	panic(wire.Build(
		NewPostService,
		NewPostUsecase,
		NewPostRepo,
	))
}
wire . NewPostUsecaseNewPostRepocleanup NewPostServiceprovidercleanupcleanup
func GetPostService() (*PostService, func(), error) {
	
	iPostRepo, cleanup, err := NewPostRepo()
	if err != nil {
		return nil, nil, err
	}
	
	iPostUsecase, cleanup2, err := NewPostUsecase(iPostRepo)
	if err != nil {
		cleanup()
		return nil, nil, err
	}
	
	postService, err := NewPostService(iPostUsecase)
	if err != nil {
		cleanup2()
		cleanup()
		return nil, nil, err
	}
	
	return postService, func() {
		cleanup2()
		cleanup()
	}, nil
}

3. 高级方法

3.1 接口注入

interfacestructstructinterfaceprovider
NewPostUsecase*PostUsecase
// NewPostUsecase NewPostUsecase
func NewPostUsecase(repo IPostRepo) (*PostUsecase, func(), error) {
	return &PostUsecase{repo: repo}, nil, nil
}
wire . IPostUsecaseprovider
▶ wire .
wire: /Go-000/Week04/blog/03_wire/01_example/wire.go:7:1: inject GetPostService: no provider found for github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.IPostUsecase
        needed by *github.com/mohuishou/go-training/Week04/blog/03_wire/01_example.PostService in provider "NewPostService" (/Go-000/Week04/blog/03_wire/01_example/example.go:36:6)
wire: github.com/mohuishou/go-training/Week04/blog/03_wire/01_example: generate failed
wire: at least one generate failure
wire.BindStruct
func GetPostService() (*PostService, func(), error) {
	
	panic(wire.Build(
		NewPostService,
		wire.Bind(new(IPostUsecase), new(*PostUsecase)), // struct 和接口进行绑定
		
		NewPostUsecase,
		NewPostRepo,
	))
}
wire.Bindwire.Bind(new(接口), new(实现))

3.2 Struct 属性注入

NewPostServicewire.Structprovider
// structType: 结构体类型
// fieldNames: 需要填充的字段,使用 "*" 表示所有字段都需要填充
Struct(structType interface{}, fieldNames ...string)

Injector
func GetPostService() (*PostService, func(), error) {
	panic(wire.Build(
		// 这里由于只有一个字段,所以这两种是等价的 wire.Struct(new(PostService), "*"),
		wire.Struct(new(PostService), "usecase"), // struct 和接口进行绑定
		wire.Bind(new(IPostUsecase), new(*PostUsecase)),
		
		NewPostUsecase,
		NewPostRepo,
	))
}

可以看到生成的代码当中自动就生成了一个结构体并且填充数据了

func GetPostService() (*PostService, func(), error) {
	
	iPostRepo, cleanup, err := NewPostRepo()
	if err != nil {
		return nil, nil, err
	}
	
	postUsecase, cleanup2, err := NewPostUsecase(iPostRepo)
	if err != nil {
		cleanup()
		return nil, nil, err
	}
	
    // 注意这里
	postService := &PostService{
		usecase: postUsecase,
	}
	
	return postService, func() {
		cleanup2()
		cleanup()
	}, nil
}

3.3 值绑定

wire.InterfaceValue

// wire.Value 为某个类型绑定值,但是不能为接口绑定值
Value(interface{}) ProvidedValue

// wire.InterfaceValue 为接口绑定值
InterfaceValue(typ interface{}, x interface{}) ProvidedValue

intio.Readera=99io.Reader = os.Stdin
// example.go

type PostService struct {
	usecase IPostUsecase
	a       int
	r       io.Reader
}

// wire.go

func GetPostService() (*PostService, func(), error) {
	panic(wire.Build(
		wire.Struct(new(PostService), "*"),                     // struct 属性注入
		
		wire.Value(10),                                         // 值绑定
		wire.InterfaceValue(new(io.Reader), os.Stdin),          // 接口值绑定
		wire.Bind(new(IPostUsecase), new(*PostUsecase)),        // 结构体绑定接口
		
		NewPostUsecase,
		NewPostRepo,
	))
}

可以看到生成的代码当中直接生成了两个全局变量

func GetPostService() (*PostService, func(), error) {
	
	iPostRepo, cleanup, err := NewPostRepo()
	if err != nil {
		return nil, nil, err
	}
	
	postUsecase, cleanup2, err := NewPostUsecase(iPostRepo)
	if err != nil {
		cleanup()
		return nil, nil, err
	}
	
	int2 := _wireIntValue               // 生成的代码绑定全局变量
	reader := _wireFileValue
	postService := &PostService{
		usecase: postUsecase,
		a:       int2,
		r:       reader,
	}
	
	
	return postService, func() {
		cleanup2()
		cleanup()
	}, nil
}

// 注意这里
var (
	_wireIntValue  = 10
	_wireFileValue = os.Stdin
)

3.4 ProviderSet(Provider 集合)

PostServiceNewPostUsecase NewPostRepoProviderSetInjectorProviderSet
// 参数是一些 provider
NewSet(...interface{}) ProviderSet

示例如下所示,生成代码和之前一样就不另外贴了

// example.go

// PostServiceSet PostServiceSet
var PostServiceSet = wire.NewSet(
	wire.Struct(new(PostService), "*"),
	
	wire.Value(10),
	wire.InterfaceValue(new(io.Reader), os.Stdin),
	wire.Bind(new(IPostUsecase), new(*PostUsecase)),
	
	NewPostUsecase,
	NewPostRepo,
)

// wire.go

func GetPostService() (*PostService, func(), error) {
	panic(wire.Build(
		PostServiceSet,
	))
}

4. wire 使用最佳实践

4.1 不要使用默认类型

provider
//错误的示例
type PostService struct {
	usecase IPostUsecase
	a       int
	b       int
	r       io.Reader
}
wireintab
▶ wire .
wire: /Go-000/Week04/blog/03_wire/01_example/example.go:40:2: provider struct has multiple fields of type int
wire: github.com/mohuishou/go-training/Week04/blog/03_wire/01_example: generate failed
wire: at least one generate failure`

自定义两个类型就好了

// 正确的做法
type A int
type B int

// PostService PostService
type PostService struct {
	usecase IPostUsecase
	a       A
	b       B
	r       io.Reader
}

// PostServiceSet PostServiceSet
var PostServiceSet = wire.NewSet(
    // 属性注入
	wire.Struct(new(PostService), "*"),
	// 值绑定
	wire.Value(A(10)),
	wire.Value(B(10)),
	wire.InterfaceValue(new(io.Reader), os.Stdin),
	// 类型绑定接口
	wire.Bind(new(IPostUsecase), new(*PostUsecase)),
	NewPostUsecase,
	NewPostRepo,
)

4.2 Option Struct

NewXXXOption Structwire.StrcutOption Strcut

// PostUsecaseOption PostUsecaseOption
type PostUsecaseOption struct {
	a    A
	b    B
	repo IPostRepo
}

// NewPostUsecase NewPostUsecase
func NewPostUsecase(opt *PostUsecaseOption) (*PostUsecase, func(), error) {
	return &PostUsecase{repo: opt.repo}, nil, nil
}

// PostServiceSet PostServiceSet
var PostServiceSet = wire.NewSet(
	wire.Struct(new(PostService), "*"),
	wire.Value(A(10)),
	wire.Value(B(10)),
	wire.InterfaceValue(new(io.Reader), os.Stdin),

    // for usecase
    wire.Bind(new(IPostUsecase), new(*PostUsecase)),
	wire.Struct(new(PostUsecaseOption), "*"),
	NewPostUsecase,

    NewPostRepo,
)

4.3 项目目录结构

.
├── api
├── cmd
│   └── app
│       ├── main.go
│       ├── wire.go
│       └── wire_gen.go
└── internal
    ├── domain
    │   └── post.go
    ├── repo
    │   └── repo.go
    ├── service
    │   └── service.go
    ├── usecase
    │   └── usecase.go
    └── wire_set.go
cmd/xxxwire.goinjectorinternalinternal/appwire_set.goProviderSetProviderSetProviderProviderusecaserepoconfigrepoProviderSetNewConfigusecasewire .

5. 参考