1. 前言
1.1 什么是依赖注入?
configlog数据库客户端
在main.go中做依赖注入,意味着在初始化代码中我们要管理:
- 依赖的初始化顺序
- 依赖之间的关系
对于小型项目而言,依赖的数量比较少,初始化代码不会很多,不需要引入依赖注入框架。但对于依赖较多的中大型项目,初始化代码又臭又长,可读性和维护性变的很差,如下一段启动函数:
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 .