一.wire简介
wire 是一个轻巧的golang依赖注入工具。它由go cloud团队开发,通过自动生成代码的方式在编译期完成依赖注入。
依赖注入是保持软件 “低耦合、易维护” 的重要设计准则之一。
此准则被广泛应用在各种开发平台之中,有很多与之相关的优秀工具。
其中最著名的当属 spring,spring ioc 作为框架的核心功能对spring的发展到今天统治地位起了决定性作用。
依赖注入很重要,所以golang社区中早已有人开发了相关工具, 比如来自uber 的 dig 、来自facebook 的 inject 。他们都通过反射机制实现了运行时依赖注入。
二.快速使用
2.1安装
安装很简单,运行 go get github.com/google/wire/cmd/wire
之后, wire 命令行工具 将被安装到 $gopath/bin
。只要确保 $gopath/bin
在 $path 中, wire 命令就可以在任何目录调用了。安装成功后运行如下命令
2.2快速入门
设计一个程序,其中 event依赖greeter,greeter依赖message
package main
import (
"fmt"
"github.com/pkg/errors"
"time"
)
type message string
func newmessage(phrase string) message {
return message(phrase)
}
type greeter struct {
message message
}
func newgreeter(m message) greeter {
return greeter{message: m}
}
func (g greeter) greet() message {
return g.message
}
type event struct {
greeter greeter // <- adding a greeter field
}
func newevent(g greeter) (event, error) {
if time.now().unix()%2 == 0 {
return event{}, errors.new("could not create event: event greeter is grumpy")
}
return event{greeter: g}, nil
}
func (e event) start() {
msg := e.greeter.greet()
fmt.println(msg)
}
如果运行event需要逐个构建依赖,代码如下
func main() {
message := newmessage("lisus2000")
greeter := newgreeter(message)
event := newevent(greeter)
event.start()
}
在此之前先介绍两个概念
provider
provider 你可以把它理解成工厂函数,这个函数的入参是依赖的属性,返回值为新一个新的类型实例
如下所示都是 provider 函数,在实际使用的时候,往往是一些简单的工厂函数,这个函数不会太复杂。
func newmessage() message {
return message("hi there!")
}
func newgreeter(m message) greeter {
return greeter{message: m}
}
injector
我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系
如下代码,我们把event、greeter、message 的工厂函数(provider)一股脑塞入wire.build()中,代表着构建 event依赖greeter、message。我们不必关心greeter、message之间的依赖关系,wire会帮我们处理
func initializeevent() event {
wire.build(newevent, newgreeter, newmessage)
return event{}
}
编写wire.go文件
//go:build wireinject
// +build wireinject
package main
import "github.com/google/wire"
var wireset = wire.newset(wire.struct(new(greeter), "message"), newmessage)
func initializeevent(phrase string) (event, error) {
//我们常常在 wire.go 文件中定义 injector ,injector也是一个普通函数,它用来声明组件之间的依赖关系
//如下代码,我们把event、greeter、message 的工厂函数(provider)一股脑塞入wire.build()中,
//代表着构建 event依赖greeter、message。我们不必关心greeter、message之间的依赖关系,wire会帮我们处理
panic(wire.build(newevent, wireset))
//return event{}, nil
}
注:使用wire生成代码时,要在代码上加以下
可以在wire.go第一行加入 //+build wireinject (与//go:build wireinject等效)注释,确保了这个文件在我们正常编译的时候不会被引用
在带有wire.go目录下运行wire命令,就会生成wire_gen.go文件,如下图所示
完整的main.go文件如下
package main
import (
"fmt"
"github.com/pkg/errors"
"time"
)
type message string
func newmessage(phrase string) message {
return message(phrase)
}
type greeter struct {
message message
}
func newgreeter(m message) greeter {
return greeter{message: m}
}
func (g greeter) greet() message {
return g.message
}
type event struct {
greeter greeter // <- adding a greeter field
}
func newevent(g greeter) (event, error) {
if time.now().unix()%2 == 0 {
return event{}, errors.new("could not create event: event greeter is grumpy")
}
return event{greeter: g}, nil
}
func (e event) start() {
msg := e.greeter.greet()
fmt.println(msg)
}
func main() {
event, _ := initializeevent("hello")
event.start()
}
,详见https://blog.csdn.net/weixin_50071922/article/details/133278161
三.基于wire构建商品微服务
3.1编写商品微服务提供者
func newgoodsappwire(logopts *log.options, registrar registry.registrar, serveropts *options.serveroptions,
rpcserver *rpcserver.server) (*gapp.app, error) {
//初始化log
log.init(logopts)
defer log.flush()
return gapp.new(
gapp.withname(serveropts.name),
gapp.withrpcserver(rpcserver),
gapp.withregistrar(registrar),
), nil
}
func newregistrar(registry *options.registryoptions) registry.registrar {
c := api.defaultconfig()
c.address = registry.address
c.scheme = registry.scheme
client, err := api.newclient(c)
if err != nil {
panic(err)
}
return consul.new(client, consul.withhealthcheck(true))
}
func newgoodsrpcserverwire(telemetry *options.telemetryoptions,
serveropts *options.serveroptions, gserver gpb.goodsserver) (*rpcserver.server, error) {
// 初始化 open-telemetry 的 exporter
trace.initagent(trace.options{
name: telemetry.name,
endpoint: telemetry.endpoint,
sampler: telemetry.sampler,
batcher: telemetry.batcher,
})
// 这里会根据 endpoint 为单元注册 trace 服务的实例
rpcaddr := fmt.sprintf("%s:%d", serveropts.host, serveropts.port)
var opts []rpcserver.serveroption
opts = append(opts, rpcserver.withaddress(rpcaddr))
if serveropts.enablelimit {
opts = append(opts, rpcserver.withunaryinterceptor(grpc.newunaryserverinterceptor()))
}
grpcserver := rpcserver.newserver(opts...)
gpb.registergoodsserver(grpcserver.server, gserver)
return grpcserver, nil
}
func newgoodsserverwire(srv v1.servicefactory) proto.goodsserver {
return &goodsserver{
srv: srv,
}
}
func newgoodsservicewire(data v1.datafactory, datasearch v12.searchfactory) servicefactory {
return &service{
data: data,
datasearch: datasearch,
}
}
func getdbfactoryor(mysqlopts *options.mysqloptions) (v1.datafactory, error) {
if mysqlopts == nil && dbfactory == nil {
return nil, errors.withcode(code.errconnectdb, "failed to get mysql store factory")
}
var err error
once.do(func() {
dsn := fmt.sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parsetime=true&loc=local",
mysqlopts.username,
mysqlopts.password,
mysqlopts.host,
mysqlopts.port,
mysqlopts.database)
//希望大家自己可以去封装logger
newlogger := logger.new(
log.new(os.stdout, "\r\n", log.lstdflags), // io writer(日志输出的目标,前缀和日志包含的内容——译者注)
logger.config{
slowthreshold: time.second, // 慢 sql 阈值
loglevel: logger.loglevel(mysqlopts.loglevel), // 日志级别
ignorerecordnotfounderror: true, // 忽略errrecordnotfound(记录未找到)错误
colorful: false, // 禁用彩色打印
},
)
db, err := gorm.open(mysql.open(dsn), &gorm.config{
logger: newlogger,
})
if err != nil {
return
}
sqldb, _ := db.db()
dbfactory = &mysqlfactory{
db: db,
}
//允许连接多少个mysql
sqldb.setmaxopenconns(mysqlopts.maxopenconnections)
//允许最大的空闲的连接数
sqldb.setmaxidleconns(mysqlopts.maxidleconnections)
//重用连接的最大时长
sqldb.setconnmaxlifetime(mysqlopts.maxconnectionlifetime)
})
if dbfactory == nil || err != nil {
return nil, errors.withcode(code.errconnectdb, "failed to get mysql store factory")
}
return dbfactory, nil
}
func getsearchfactoryor(opts *options.esoptions) (v12.searchfactory, error) {
if opts == nil && searchfactory == nil {
return nil, errors.new("failed to get es client")
}
once.do(func() {
esopt := db.esoptions{
host: opts.host,
port: opts.port,
}
esclient, err := db.newesclient(&esopt)
if err != nil {
return
}
searchfactory = &datasearch{
esclient: esclient,
}
})
if searchfactory == nil {
return nil, errors.new("failed to get es client")
}
return searchfactory, nil
}
3.2编写wire.go文件
//go:build wireinject
// +build wireinject
package srv
import (
"github.com/google/wire"
v1 "mxshop/app/goods/srv/internal/controller/v1"
"mxshop/app/goods/srv/internal/data/v1/data_search/v1/es"
"mxshop/app/goods/srv/internal/data/v1/db"
v12 "mxshop/app/goods/srv/internal/service/v1"
"mxshop/app/pkg/options"
gapp "mxshop/gmicro/app"
"mxshop/pkg/log"
)
func initapp(*log.options, *options.serveroptions, *options.registryoptions,
*options.telemetryoptions, *options.mysqloptions, *options.esoptions) (*gapp.app, error) {
wire.build(newgoodsappwire,
newregistrar,
newgoodsrpcserverwire,
v1.newgoodsserverwire,
v12.newgoodsservicewire,
db.getdbfactoryor,
es.getsearchfactoryor,
)
return &gapp.app{}, nil
}
3.3生成wire_gen.go文件
生成后的文件如下:
// code generated by wire. do not edit.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package srv
import (
v1_2 "mxshop/app/goods/srv/internal/controller/v1"
"mxshop/app/goods/srv/internal/data/v1/data_search/v1/es"
"mxshop/app/goods/srv/internal/data/v1/db"
"mxshop/app/goods/srv/internal/service/v1"
"mxshop/app/pkg/options"
"mxshop/gmicro/app"
"mxshop/pkg/log"
)
// injectors from wire.go:
func initapp(logoptions *log.options, serveroptions *options.serveroptions, registryoptions *options.registryoptions, telemetryoptions *options.telemetryoptions, mysqloptions *options.mysqloptions, esoptions *options.esoptions) (*app.app, error) {
registrar := newregistrar(registryoptions)
datafactory, err := db.getdbfactoryor(mysqloptions)
if err != nil {
return nil, err
}
searchfactory, err := es.getsearchfactoryor(esoptions)
if err != nil {
return nil, err
}
servicefactory := v1.newgoodsservicewire(datafactory, searchfactory)
goodsserver := v1_2.newgoodsserverwire(servicefactory)
server, err := newgoodsrpcserverwire(telemetryoptions, serveroptions, goodsserver)
if err != nil {
return nil, err
}
appapp, err := newgoodsappwire(logoptions, registrar, serveroptions, server)
if err != nil {
return nil, err
}
return appapp, nil
}
在如下方法替换接口
运行main.go文件,如下显示,正常启动
发表评论