golang 依赖注入 wire 和 dig 体验对比

在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 两者之间的特点:

  1. dig 通过反射识别依赖关系,wire 是编译前计算依赖关系
  2. dig 只能在代码运行时,才能知道哪个依赖不对,比如构造函数返回类型的是结构体指针,但是其他依赖的是interface,这样的错误只能在运行时发现,而wire可以在编译的时候就发现。
  3. 由于采用了依赖注入,所以在代码调试时可以注入一些mock 服务或者函数,wire在mock上支持更友好些,dig的话可以通过build tag 来使用mock。 个人比较推荐使用wire,可以在编译时就发现问题,避免了 多次的build和尝试后才解决编译问题。更多的使用方式和最佳实践,可以参考官方文档。

0x04 参考

wire

dig