Dependency injection
一個Go編程語言的運行依賴注入庫。依賴注入是更廣泛的控制反轉技術的一種形式。它用於增加程序的模塊化並使其具有可擴展性。
實例展示(High API):
type A struct {
Name string
}
func NewA() *A {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
name := "A-" + strconv.Itoa(r.Int())
return &A{Name: ls}
}
services := NewServiceCollection()
services.AddSingleton(NewA)
//serviceCollection.AddSingletonByImplementsAndName("redis-master", NewRedis, new(abstractions.IDataSource))
//serviceCollection.AddTransientByImplements(NewRedisClient, new(redis.IClient))
//serviceCollection.AddTransientByImplements(NewRedisHealthIndicator, new(health.Indicator))
serviceProvider := services.Build()
var env *A
_ = serviceProvider.GetService(&env) // used
依賴注入將如何幫助我們的?
依賴注入是更廣泛的控制反轉技術的一種形式。它用於增加程序的模塊化並使其具有可擴展性。
安裝
go get -u github.com/yoyofxteam/[email protected]
組件特性
- Providing
- Extraction
- Invocation
- Lazy-loading
- Interfaces
- Groups
- Named definitions
- Optional parameters
- Parameter Bag
- Prototypes
- Cleanup
Providing
首先,我們需要創建兩個基本類型:http。服務器和http.ServeMux。讓我們創建一個簡單的構造函數來初始化它:
// NewServer creates a http server with provided mux as handler.
func NewServer(mux *http.ServeMux) *http.Server {
return &http.Server{
Handler: mux,
}
}
// NewServeMux creates a new http serve mux.
func NewServeMux() *http.ServeMux {
return &http.ServeMux{}
}
支持的構造器簽名形式如下:
func([dep1, dep2, depN]) (result, [cleanup, error])
現在讓我們來容器構建這些類型。
下面展示是Low API, 也可以用High API來構建:
import (
di "github.com/yoyofxteam/dependencyinjection"
)
container := di.New(
// provide http server
di.Provide(NewServer),
// provide http serve mux
di.Provide(NewServeMux)
)
函數di. new()解析構造函數,編譯依賴關係圖並返回*di。用於交互的容器類型。如果無法編譯,容器會出現恐慌。
我認爲在應用程序初始化時而不是在運行時出現恐慌是很常見的。
Extraction
我們可以從容器中提取構建的服務器。爲此,定義提取類型的變量,並將變量指針傳遞給Extract函數。
如果未找到提取的類型或構建實例的過程導致錯誤,則提取返回錯誤。
如果沒有錯誤發生,我們就可以使用這個變量,就像我們自己構建它一樣。
// declare type variable
var server *http.Server
// extracting
err := container.Extract(&server)
if err != nil {
// check extraction error
}
server.ListenAndServe()
請注意,默認情況下,容器作爲單例創建實例 , 但也可使用Provide做行爲上的改變。
## Invocation
作爲提取的替代方法,我們可以使用Invoke()函數。它解析函數依賴並調用函數。調用函數可能返回可選錯誤。
```golang
// NewServer creates a http server with provided mux as handler.
func NewServer(handler http.Handler) *http.Server {
return &http.Server{
Handler: handler,
}
}
對於一個容器來說,要知道作爲http的實現。處理程序是必須使用的,我們使用選項di.As()。這個選項的參數必須是一個指向接口的指針,比如new(Endpoint)。
這種語法可能看起來很奇怪,但我還沒有找到更好的方式來指定接口。
修改依賴注入容器初始化代碼:
container := inject.New(
// provide http server
inject.Provide(NewServer),
// provide http serve mux as http.Handler interface
inject.Provide(NewServeMux, inject.As(new(http.Handler)))
)
現在容器使用提供*http。ServeMux作爲http。服務器構造函數中的處理程序。使用接口有助於編寫更多可測試的代碼。
Groups
容器自動將接口的所有實現分組到[]
讓我們使用這個特性添加一些http控制器。控制器有典型的行爲。它正在註冊路由。首先,將爲它創建一個接口。
// Controller is an interface that can register its routes.
type Controller interface {
RegisterRoutes(mux *http.ServeMux)
}
現在我們將編寫控制器並實現控制器接口。
// OrderController is a http controller for orders.
type OrderController struct {}
// NewOrderController creates a auth http controller.
func NewOrderController() *OrderController {
return &OrderController{}
}
// RegisterRoutes is a Controller interface implementation.
func (a *OrderController) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/orders", a.RetrieveOrders)
}
// Retrieve loads orders and writes it to the writer.
func (a *OrderController) RetrieveOrders(writer http.ResponseWriter, request *http.Request) {
// implementation
}
UserController
// UserController is a http endpoint for a user.
type UserController struct {}
// NewUserController creates a user http endpoint.
func NewUserController() *UserController {
return &UserController{}
}
// RegisterRoutes is a Controller interface implementation.
func (e *UserController) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("/users", e.RetrieveUsers)
}
// Retrieve loads users and writes it using the writer.
func (e *UserController) RetrieveUsers(writer http.ResponseWriter, request *http.Request) {
// implementation
}
就像在這個接口的例子中,我們將使用inject.As()提供provide選項。
container := inject.New(
di.Provide(NewServer), // provide http server
di.Provide(NewServeMux), // provide http serve mux
// endpoints
di.Provide(NewOrderController, di.As(new(Controller))), // provide order controller
di.Provide(NewUserController, di.As(new(Controller))), // provide user controller
)
現在,我們可以在mux中使用控制器數組。參見更新後的代碼:
// NewServeMux creates a new http serve mux.
func NewServeMux(controllers []Controller) *http.ServeMux {
mux := &http.ServeMux{}
for _, controller := range controllers {
controller.RegisterRoutes(mux)
}
return mux
}
高級功能
Named definitions
在某些情況下,一種類型有多個實例。例如兩個數據庫實例:master -用於寫,slave -用於讀。
// MasterDatabase provide write database access.
type MasterDatabase struct {
*Database
}
// SlaveDatabase provide read database access.
type SlaveDatabase struct {
*Database
}
第二種方法是使用帶有di.WithName()提供選項的命名定義
// provide master database
di.Provide(NewMasterDatabase, di.WithName("master"))
// provide slave database
di.Provide(NewSlaveDatabase, di.WithName("slave"))
如果需要從容器中提取,請使用di.Name()提取選項。
var db *Database
container.Extract(&db, di.Name("master"))
如果需要在其他構造函數中提供命名定義,請使用di。參數嵌入。
// ServiceParameters
type ServiceParameters struct {
di.Parameter
// use `di` tag for the container to know that field need to be injected.
MasterDatabase *Database `di:"master"`
SlaveDatabase *Database `di:"slave"`
}
// NewService creates new service with provided parameters.
func NewService(parameters ServiceParameters) *Service {
return &Service{
MasterDatabase: parameters.MasterDatabase,
SlaveDatabase: parameters.SlaveDatabase,
}
}
Optional parameters
// ServiceParameter
type ServiceParameter struct {
di.Parameter
StdOutLogger *Logger `di:"stdout"`
FileLogger *Logger `di:"file,optional"`
}
將依賴聲明爲可選的構造函數必須處理這些依賴不存在的情況。
Parameter Bag
如果需要在定義級別上指定一些參數,則可以使用 inject.ParameterBag provide option. 這是一個map[string]interface{},可以轉換爲di.ParameterBag類型。
// Provide server with parameter bag
di.Provide(NewServer, di.ParameterBag{
"addr": ":8080",
})
// NewServer create a server with provided parameter bag. Note: use di.ParameterBag type.
// Not inject.ParameterBag.
func NewServer(pb di.ParameterBag) *http.Server {
return &http.Server{
Addr: pb.RequireString("addr"),
}
}
Prototypes
如果您想在每次提取上創建一個新實例,請使用di.Prototype()提供選項。
di.Provide(NewRequestContext, di.Prototype())
Cleanup
如果提供程序創建了一個需要清理的值,那麼它可以返回一個閉包來清理資源。
func NewFile(log Logger, path Path) (*os.File, func(), error) {
f, err := os.Open(string(path))
if err != nil {
return nil, nil, err
}
cleanup := func() {
if err := f.Close(); err != nil {
log.Log(err)
}
}
return f, cleanup, nil
}
在調用container.Cleanup()之後,它遍歷實例,如果存在則調用cleanup函數。
container := di.New(
// ...
di.Provide(NewFile),
)
// do something
container.Cleanup() // file was closed
總結
Dependency injection 是一個運行時的依賴注入框架, 它也會集成到了微服務框架 yoyogo 中. 🦄🌈 YoyoGo (Go語言框架)一個簡單、輕量、快速、基於依賴注入的微服務框架( web 、grpc ),支持Nacos/Consoul/Etcd/Eureka/k8s /Apollo等 .
參考:
yoyogo: https://github.com/yoyofx/yoyogo
dependencyinjection: https://github.com/yoyofxteam/dependencyinjection