Go 開源庫運行時依賴注入框架 Dependency injection

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

容器自動將接口的所有實現分組到[]組。例如,提供with inject.As(new(http.Handler))自動創建一個組[]http.Handler。

讓我們使用這個特性添加一些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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章