今天我们来讲一讲 Wire 的入门使用。
Wire 是什么
go 的依赖注入工具常见有2种,一类是通过反射在运行时进行依赖注入,典型代表是 uber 开源的 dig,另外一类是通过 generate 进行代码生成,典型代表就是我今天要将的 Google 开源的 wire。使用 dig 功能会强大一些,但是缺点就是错误只能在运行时才能发现,这样如果不小心的话可能会导致一些隐藏的 bug 出现。使用 wire 的缺点就是功能限制多一些,但是好处就是编译的时候就可以发现问题,并且生成的代码其实和我们自己手写相关代码差不太多,更符合直觉,心智负担更小。
Wire 使用
安装
Wire 的安装十分简单, 只要执行 go get github.com/google/wire/cmd/wire , wire 命令行工具就会被安装到 $GOPATH/bin 目录下。
核心概念
在正式使用前,我来介绍一下 Wire 中的2个重要概念: Provider 和 Injector。
Provider
Provider 是一个普通的函数,这个函数会返回构建依赖关系所需的组件。如下所示,就是一个 provider 函数,在实际使用的时候,往往是一些简单工厂函数。
func NewDb(opt *DbOpt)(*Db, error){...}
在使用中,不能存在2个 Provider 返回相同的类型。
Injector
Injector 是由 wire 自动生成的函数。函数内部会按根据依赖顺序调用相关 Provider。
我们往往在 wire.go 文件中定义 Injector 函数签名。然后通过 wire 命令行工具生成完整函数。由于 wire.go 中的函数并没有真正返回值,为避免编译器报错, 简单地用 panic 函数包装起来即可。不用担心执行时报错, 因为它不会实际运行,只是用来生成真正的代码的依据。
下面看一个 wire.go 的例子:
func initApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) {
panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))
}// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package main
import (
"helloworld/internal/biz"
"helloworld/internal/conf"
"helloworld/internal/data"
"helloworld/internal/server"
"helloworld/internal/service"
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/log"
"github.com/google/wire"
)
// initApp init kratos application.
func initApp(*conf.Server, *conf.Data, log.Logger) (*kratos.App, func(), error) {
panic(wire.Build(server.ProviderSet, data.ProviderSet, biz.ProviderSet, service.ProviderSet, newApp))
}
运行 wire . 命令就会生成 wire_gen.go 文件, 会生成 Injector 函数的真正实现, wire_gen.go:
// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//+build !wireinject
package main
import (
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/log"
"helloworld/internal/biz"
"helloworld/internal/conf"
"helloworld/internal/data"
"helloworld/internal/server"
"helloworld/internal/service"
)
// Injectors from wire.go:
// initApp init kratos application.
func initApp(confServer *conf.Server, confData *conf.Data, logger log.Logger) (*kratos.App, func(), error) {
dataData, cleanup, err := data.NewData(confData, logger)
if err != nil {
return nil, nil, err
}
greeterRepo := data.NewGreeterRepo(dataData, logger)
greeterUsecase := biz.NewGreeterUsecase(greeterRepo, logger)
greeterService := service.NewGreeterService(greeterUsecase, logger)
httpServer := server.NewHTTPServer(confServer, greeterService, logger)
grpcServer := server.NewGRPCServer(confServer, greeterService, logger)
app := newApp(logger, httpServer, grpcServer)
return app, func() {
cleanup()
}, nil
}
然后我们就可以使用生成的函数了,如下:
package main
import (
"flag"
"os"
"helloworld/internal/conf"
"github.com/go-kratos/kratos/v2"
"github.com/go-kratos/kratos/v2/config"
"github.com/go-kratos/kratos/v2/config/file"
"github.com/go-kratos/kratos/v2/log"
"github.com/go-kratos/kratos/v2/transport/grpc"
"github.com/go-kratos/kratos/v2/transport/http"
)
func main() {
// ...
app, cleanup, err := initApp(bc.Server, bc.Data, logger)
if err != nil {
panic(err)
}
defer cleanup()
// start and wait for stop signal
if err := app.Run(); err != nil {
panic(err)
}
}
如果不小心忘记了某个 Provider, wire 会报出具体的错误, 帮忙开发人员迅速定位问题。