go微服務框架kratos學習筆記八(kratos的依賴注入)
筆記二提過依賴注入,和如何生成,但沒有細講,本文來簡單看看kratos的依賴注入。
什么是依賴注入
先來看一個小程序,
創建一個小程序模擬迎賓員問候客人的事件
我們將創建三個結構類型:
1)為迎賓員創建消息 message
2)表達消息的迎賓員 greeter
3)迎賓員問候客人的事件 event
type Message string
type Greeter struct {
// ... TBD
}
type Event struct {
// ... TBD
}
Message 僅僅攜帶一條string,現在我們創建一個簡單初始器
func NewMessage() Message {
return Message("Hi there!")
}
我們的Greeter將需要引用這條消息,讓我們創建一條初始器給Greeter
func NewGreeter(m Message) Greeter {
return Greeter{Message: m}
}
type Greeter struct {
Message Message // <- adding a Message field
}
在這個初始器中我們分配了Message 字段給Greeter,現在我們能用Greeter的方法Greet來得到一條Message
func (g Greeter) Greet() Message {
return g.Message
}
下一步,我們同樣也需要一個初始器用Event來創建一個Greeter。
func NewEvent(g Greeter) Event {
return Event{Greeter: g}
}
type Event struct {
Greeter Greeter // <- adding a Greeter field
}
添加一個Start()來啟動事件
func (e Event) Start() {
msg := e.Greeter.Greet()
fmt.Println(msg)
}
Start即是我們小程序的核心,它告訴greeter去放出一條問候並打印出來。
現在我們小程序所有的組件就緒了,看看它是如何初始化所有組件的,它看起來可能是這樣的
func main() {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
event.Start()
}
首先我們創建一條message,然后我們用message創建一個greeter,最后我們用greeter創建一個event.
這其實就是依賴注入dependency injection簡稱di的原理,
依賴注入基本上就是提供對象需要的對象(其依賴),而不是讓對象自己構造它們。
依賴注入能讓測試變的更為簡單,我們可以通過構造函數來進行注入。
SomeClass() has its constructor as following:
public SomeClass() {
myObject = Factory.getObject();
}
例如,如果myObject包含復雜的任務像磁盤訪問或者網絡訪問,那么SomeClass將很難進行單元測試。程序必須模仿myObject並且需要模仿Factory調用
而將myObject作為參數傳遞給構造函數.
public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
myObject就能直接運行,使測試變的更為簡單。
可以通過多種方式將依賴項注入到對象中(例如構造函數注入或setter注入)。甚至可以使用專門的依賴項注入框架(例如Spring)來做到這一點,但是肯定不是必需的。不需要那些框架的依賴注入。顯式實例化和傳遞對象(依賴項)與框架注入一樣好。
google wire
kratos 使用的 google wire 就是golang的一個依賴注入解決的工具,這個工具能夠自動生成類的依賴關系。
依賴注入的一個缺點就是需要如此多的初始化步驟,讓我們看看如何使用Wire來讓初始化我們的組件變的更快.
將我們的小程序main函數改成如下形式:
func main() {
e := InitializeEvent()
e.Start()
}
下一步,分離一個文件wire.go,我們定義InitializeEvent
// wire.go
func InitializeEvent() Event {
wire.Build(NewEvent, NewGreeter, NewMessage)
return Event{}
}
不是依次初始化每個組件並將其傳遞給下一個組件,而是通過一個 wire.Build調用來構建我們想要的用的初始器。在Wire中,初始化器被稱為providers,一個提供特定類型的函數。
我們為Event添加一個零值作為返回值,以滿足編譯器的要求。
注意,即使我們向Event添加值,Wire也會忽略它們。
實際上,注入器的目的是提供關於使用哪些providers 來構造Event的信息。
InitializeEvent即是一個“注入器”。現在我們已經完成了注入器
然后在wire.go目錄下運行wire工具。
安裝 :
go get github.com/google/wire/cmd/wire
Wire將找到InitializeEvent注入器並生成一個函數,其主體由所有必要的初始化步驟填充。結果將被寫入名為wire_gen.go的文件。
// wire_gen.go
func InitializeEvent() Event {
message := NewMessage()
greeter := NewGreeter(message)
event := NewEvent(greeter)
return event
}
這看上去就像我們上面手工寫的代碼,想象一下,對於復雜得多的組件,Wire是多么有用。
kratos中的wire
最后回來看看kratos中的wire.go
// +build wireinject
// The build tag makes sure the stub is not built in the final build.
package di
import (
pb "demo/api"
"demo/internal/dao"
"demo/internal/server/grpc"
"demo/internal/server/http"
"demo/internal/service"
"github.com/google/wire"
)
var daoProvider = wire.NewSet(dao.New, dao.NewDB, dao.NewRedis, dao.NewMC)
var serviceProvider = wire.NewSet(service.New, wire.Bind(new(pb.DemoServer), new(*service.Service)))
func InitApp() (*App, func(), error) {
panic(wire.Build(daoProvider, serviceProvider, http.New, grpc.New, NewApp))
}
可以看到kratos用到了wire的一些其它接口:wire.NewSet,wire.Bind,簡單看看。
wire.NewSet
Wire有兩個核心概念:Providers 和injectors。
Providers
Wire中的主要機制是Providers :一個可以生成值的函數。這些函數都是普通的Go代碼。
Providers 可以分組為provider sets,通過wire.NewSet 函數可以添加一組providers 到一個新的集合中。
var daoProvider = wire.NewSet(dao.New, dao.NewDB, dao.NewRedis, dao.NewMC)
當然也可以添加一個provider sets進一個provider sets
var MegaSet = wire.NewSet(daoProvider, pkg.OtherSet)
injector(注入器)
一個應用程序用injector連接這些providers: 一個按依賴順序調用providers的函數,即使用Wire,編寫注入器的簽名,然后Wire生成函數的主體。
調用wire.Build的函數則是來聲明注入器的,返回值不重要,只要類型正確即可。
func InitApp() (*App, func(), error) {
panic(wire.Build(daoProvider, serviceProvider, http.New, grpc.New, NewApp))
}
Binding Interfaces
最后看看 wire.Bind 用來綁定接口的具體類型。
var serviceProvider = wire.NewSet(service.New, wire.Bind(new(pb.DemoServer), new(*service.Service)))
wire.Bind第一個參數 為指向所需接口類型的指針,第二個參數為 指向實現該接口類型的指針,
可以看到如果不加wire.Bind(new(pb.DemoServer), new(*service.Service)), 可以看到會找不到依賴。

