一.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 mainimport ("fmt""github.com/pkg/errors""time"
)type Message stringfunc 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 wireinjectpackage mainimport "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 mainimport ("fmt""github.com/pkg/errors""time"
)type Message stringfunc 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()
}
wire的進階,詳見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) {//初始化loglog.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.Addressc.Scheme = registry.Schemeclient, 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 的 exportertrace.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.ServerOptionopts = 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 erroronce.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)//希望大家自己可以去封裝loggernewLogger := 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,}//允許連接多少個mysqlsqlDB.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 wireinjectpackage srvimport ("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 !wireinjectpackage srvimport (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文件,如下顯示,正常啟動