背景
wire 是 google 提供的自动化的依赖注入工具, 能够帮助我们梳理各个模块的依赖关系, 并自动实现依赖注入。 以后端服务常见的service, biz, data 三层结构为例, 通常是 service 调用 biz, biz 调用 data, 也就是 service 启动要依赖于biz 的启动, biz 的启动要依赖于 data的启动。 随着服务的复杂, 比如一个 service依赖于多个biz, 多个biz依赖于多个data, 如果通过人工去处理这里面各个层级的构建,启动,将会是非常耗时且容易出错的。 这时候wire就派上用场了。wire这个名字非常生动形象, 就像在一团乱麻中找出线头和线尾
极简案例
overview
这篇教程将展示下面这层依赖关系如何使用wire进行依赖注入
text
service
/ \
biz1 -> biz2, biz2依赖于biz1的启动
/ \ / \
dao1 dao2 dao1 dao2
上图的依赖关系是 service1 依赖于 biz1和biz2, biz1 依赖于 dao1 和 dao2, biz2 依赖于biz1, 也依赖于 dao1和dao2
如果用人脑进行思考, 那么就要先启动 dao1和 dao2, 然后使用dao1和dao0去启动 biz1, 然后使用 biz1, dao1 和 dao2 去启动 biz2。 注意这里要先启动biz1 再启动biz2, 因为biz2依赖于biz1的启动. 最后再用biz1和biz2去自动service.
可以看到上面这个过程还是比较简单的, 但实际的服务种可能有好几个 dao(mysql, redis, kafka, rabbitmq 等各种数据库), 十几个biz
这种情况下再用人工去梳理就不太现实了, 一旦梳理漏了就可能导致严重的事故
下面用代码来看看如何使用 wire 来构建上面的service 首先创建一个空目录 wire_demo, 然后在此目录下运行
shell
go mod init wire demo
go mod tidy
然后来看一下文件的目录结构
text
dao
dao1.go dao1的new函数
dao2.go dao2的new函数
wire.go 使用wire.build 提供一个 ProviderSet , 里面用到了 dao 下全部的 new函数
biz
biz1.go biz1的new函数, 依赖于 dao1, dao2
biz2.go biz2的new函数, 依赖于 dao1, dao2
wire.go 使用wire.build 提供一个 ProviderSet , 里面用到了 biz 下全部的 new函数
service
service.go service的new函数, 依赖于biz1和biz2
wire.go 使用wire.build 提供一个 ProviderSet , 里面用到了 service 下的 new函数
cmd
wire.go 包含一个 wireSimpleService函数, 里面使用wire.Build传入构建service需要用到的全部ProviderSet, 告诉wire返回一个 service. 顶部的两行注释告诉go这是一个wire文件只参与构建, 不会被main函数用到。
wire_gen.go 使用wire构建出来的依赖注入过程, 这个文件不需要我们手下, 而是由wire自动生成的。这个文件内也有一个wireSimpleService函数, 也就是 先启动 dao1和 dao2, 然后使用dao1和dao0去构建 biz1, 然后使用 biz1, dao1 和 dao2 去构建 biz2, 最后再用biz1和biz2去构建service并返回service
main.go 调用wire生成的 wireSimpleService 函数得到一个 service, 并调用 Run()函数启动这个service
go.mod
go.sum
其中的 wire_gen.go, go.mod, go.sum 都是自动生成的我们不需要手写, 每个文件的作用我都写在上面了,简单看一下即可
dao目录
dao 目录下有3个文件 dao1.go, dao2.go, wire.go dao1.go 和 dao2.go 里面定义了 dao1 和 dao2 两个 dao 层的结构体以及对应的new 函数 wire.go 提供了一个 wire.set 类型的 ProviderSet 里面包含了两个dao的new函数
dao1.go
go
package dao
import "fmt"
type Dao1 struct {
}
func NewDao1() *Dao1 {
dao := &Dao1{}
fmt.Println("dao1 start ok")
return dao
}
dao2.go
go
package dao
import "fmt"
type Dao2 struct {
}
func NewDao2() *Dao2 {
dao := &Dao2{}
fmt.Println("dao2 start ok")
return dao
}
wire.go
go
package dao
import "github.com/google/wire"
var ProviderSet = wire.NewSet(NewDao1, NewDao2)
biz 目录
biz 目录下有3个文件 biz1.go, biz2.go, wire.go biz1.go 和 biz2.go 里面定义了 biz1 和 biz2 两个 biz 层的结构体以及对应的new 函数 wire.go 提供了一个 wire.set 类型的 ProviderSet 里面包含了两个biz的new函数
biz1.go
go
package biz
import (
"fmt"
"wire_dmo/dao"
)
type Biz1 struct {
dao1 *dao.Dao1
dao2 *dao.Dao2
}
func NewBiz1(dao1 *dao.Dao1, dao2 *dao.Dao2) *Biz1 {
biz := &Biz1{
dao1: dao1,
dao2: dao2,
}
fmt.Println("biz1 start ok")
return biz
}
biz2.go
go
package biz
import (
"fmt"
"wire_dmo/dao"
)
type Biz2 struct {
dao1 *dao.Dao1
dao2 *dao.Dao2
biz1 *Biz1
}
func NewBiz2(dao1 *dao.Dao1, dao2 *dao.Dao2, biz1 *Biz1) *Biz2 {
biz := &Biz2{
dao1: dao1,
dao2: dao2,
biz1: biz1,
}
fmt.Println("biz2 start ok")
return biz
}
wire.go
go
package biz
import "github.com/google/wire"
var ProviderSet = wire.NewSet(NewBiz1, NewBiz2)
serivce 目录
biz 目录下有2个文件 service.go, wire.go service.go 定义了 service 层的结构体, 其对应的 new 函数, 以及一个 Run 函数用来表示 service已经正常启动, 其内部间隔1秒输出字符串然后终止运行 wire.go 提供了一个 wire.set 类型的 ProviderSet 里面包含了service的new函数
service.go
go
package service
import (
"fmt"
"time"
"wire_dmo/biz"
)
type Service struct {
biz1 *biz.Biz1
biz2 *biz.Biz2
}
func NewService(biz1 *biz.Biz1, biz2 *biz.Biz2) *Service {
service := &Service{
biz1: biz1,
biz2: biz2,
}
fmt.Println("service start ok")
return service
}
func (s *Service) Run() {
for i := 0; i < 3; i++ {
fmt.Println("service is running")
time.Sleep(1 * time.Second)
}
fmt.Println("service stop running")
}
wire.go
go
package service
import "github.com/google/wire"
var ProviderSet = wire.NewSet(NewService)
cmd目录
cmd目录下有3公文件, main.go, wire.go, wire_gen.go , 其中 前两个文件是我们手写的, 最后一个wire_gem.go是wire生成的
先来看 wire.go
go
//go:build wireinject
// +build wireinject
package main
import (
"github.com/google/wire"
"wire_dmo/biz"
"wire_dmo/dao"
"wire_dmo/service"
)
func wireSimpleService() *service.Service {
wire.Build(
service.ProviderSet,
biz.ProviderSet,
dao.ProviderSet,
)
return &service.Service{}
}
其内部有一个 wireSimpleService 函数, 调用了 wire.Build , 这个wire.Build可以看作是 wire 构建的入口, 我们只需要提供每一个层级的 providerset 给它即可, 最后告诉它需要return什么, wire就会自动构建整个依赖关系
我们在cmd目录下运行
shell
wire gen .
即可生成 wire_gen.go 文件
go
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package main
import (
"wire_dmo/biz"
"wire_dmo/dao"
"wire_dmo/service"
)
// Injectors from wire.go:
func wireSimpleService() *service.Service {
dao1 := dao.NewDao1()
dao2 := dao.NewDao2()
biz1 := biz.NewBiz1(dao1, dao2)
biz2 := biz.NewBiz2(dao1, dao2, biz1)
serviceService := service.NewService(biz1, biz2)
return serviceService
}
可以看到最上面的注释告诉我们这个文件是wire自动生成的,不要手动修改, wireSimpleService函数内部的构建顺序和我们人脑思考的结果完全一致。
最后的main.go
go
package main
func main() {
simpleService := wireSimpleService()
simpleService.Run()
}
如果点击 wireSimpleService() 会跳转到 wire_gen.go 中的 wireSimpleService()去 而不是 wire.go中的wireSimpleService(), 这是因为在 wire.go 文件顶部加了这两行
go
//go:build wireinject
// +build wireinject
运行起来
最终运行 main.go 会输出每一个dao, biz, service的构建过程, 最终启动Run函数, 3次输出后终止运行, 整个过程符合预期
text
dao1 start ok
dao2 start ok
biz1 start ok
biz2 start ok
service start ok
service is running
service is running
service is running
service stop running