在Go编程中,往往会存在大量的全局变量,有时候会被吐槽全局变量满天飞,如何解决,依赖注入库值得拥有;使用了Uber di 库和 Google wire 库后,谈下使用感受。
0x01 dig
uber 推出的依赖注入库,采用反射,在运行时计算依赖关系,构造依赖对象。在使用依赖注入库时,我们需要将我们的构造函数注入到contain中,下面是使用示例:
package main
import (
"fmt"
"go.uber.org/dig"
)
type User struct {
ID int64
}
type Repo interface {
Get(int64) (*User, error)
}
type repo struct{}
func (r *repo) Get(id int64) (*User, error) {
return &User{ID: id}, nil
}
func NewRepo() Repo {
return &repo{}
}
type Service struct {
r Repo
}
func NewService(r Repo) *Service {
return &Service{r: r}
}
func main() {
container := dig.New()
container.Provide(NewRepo)
container.Provide(NewService)
container.Invoke(func(s *Service) {
u, err := s.r.Get(1)
fmt.Printf("user %+v err %+v", u, err)
})
}
上面的例子中涉及两种对象类型构造,一种是struct 指针,一种是 interface 类型。dig会自动做类型判定和绑定。在容器中的对象都是单例对象。dig还有对注入的对象进行分组特性,在使用时,根据自身需要使用。
0x02 wire
Google 推出的依赖注入库,在build前,需要时用wire 工具生成代码,wire使用了go build的tag选项,在编译时可以选择到正确的编译文件,但是有个小坑,容我慢慢道来
//+build wireinject
package main
import (
"fmt"
"log"
)
import "github.com/google/wire"
type User struct {
ID int64
}
type Repo interface {
Get(int64) (*User, error)
}
type repo struct{}
func (r *repo) Get(id int64) (*User, error) {
return &User{ID: id}, nil
}
func NewRepo() Repo {
return &repo{}
}
var (
Set = wire.NewSet(
NewRepo,
)
)
type Service struct {
R Repo
}
func NewService(r Repo) (*Service, error) {
return &Service{R: r}, nil
}
func InitApp() (*Service, error) {
panic(wire.Build(Set, NewService))
}
func main() {
s, err := InitApp()
if err != nil {
log.Fatal(err)
return
}
u, err := s.R.Get(1)
fmt.Printf("%+v,+%v", u, err)
}
wire 要求在编译前通过执行 wire gen 指令,手动生成依赖关系的函数;下面这段是wire 对依赖计算后的代码:
// Injectors from main.go:
func InitApp() (*Service, error) {
mainRepo := NewRepo()
service, err := NewService(mainRepo)
if err != nil {
return nil, err
}
return service, nil
}
wire 重新生成了InitApp 函数,在编译时选取wire生成的文件。严格意义上说,wire不算是库,算是依赖计算工具,解放我们的双手,帮助我们计算依赖关系和生成相应代码;不方便的点是wire 的bind方式绑定struct和interface , IDE 不能帮助提示到哪个接口哪个函数没有实现;下面是上面图带陷阱说明示例:
//+build wireinject
package main
上面这段代码,如果build tag 和 package 之间没有空格,build tag识别不了,编译就会报重复声明,wire上也已经有issue提到此问题。
0x03 小结
上面简单介绍了wire 和 dig 两者之间的特点:
- dig 通过反射识别依赖关系,wire 是编译前计算依赖关系
- dig 只能在代码运行时,才能知道哪个依赖不对,比如构造函数返回类型的是结构体指针,但是其他依赖的是interface,这样的错误只能在运行时发现,而wire可以在编译的时候就发现。
- 由于采用了依赖注入,所以在代码调试时可以注入一些mock 服务或者函数,wire在mock上支持更友好些,dig的话可以通过build tag 来使用mock。 个人比较推荐使用wire,可以在编译时就发现问题,避免了 多次的build和尝试后才解决编译问题。更多的使用方式和最佳实践,可以参考官方文档。