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{}
}
现在让我们来容器构建这些类型。
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()
## 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