首页 > 其他分享 >Go 开源库运行时依赖注入框架 Dependency injection

Go 开源库运行时依赖注入框架 Dependency injection

时间:2023-06-02 11:57:01浏览次数:43  
标签:provide http di Provide mux Dependency func Go 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 中.

标签:provide,http,di,Provide,mux,Dependency,func,Go,injection
From: https://www.cnblogs.com/maxzhang1985/p/17451316.html

相关文章

  • ssh远程redhat6报错:Unable to negotiate with *.*.*.* port 22: no matching host key
    报错:Unabletonegotiatewith*.*.*.*port22:nomatchinghostkeytypefound.Theiroffer:ssh-rsa,ssh-dss分析:openssh觉得ssh-rsa加密方式不安全,直接从8.8开始默认不允许这种密钥用于登陆了 解决:cat/etc/ssh/ssh_config.d/redhat6.confHost*PubkeyAcceptedKe......
  • golang实现设计模式之享元模式总结-代码、优缺点、适用场景
    享元模式是一种结构型的设计模式,通过共享细粒度对象实现对象的复用,从而达到减少对象创建与销毁,减少内存消耗,其本质是本质是缓存共享对象,降低内存消耗。享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象......
  • STL algorithm算法
    Functionsin<algorithm>Non-modifyingsequenceoperations:all_ofTestconditiononallelementsinrange(functiontemplate)any_ofTestifanyelementinrangefulfillscondition(functiontemplate)none_ofTestifnoelementsfulfillconditi......
  • Provider parse errors: Cannot instantiate cyclic dependency! ApplicationRef ("[E
    异常: 异常的原因:自定义的一个全局异常类,在它的构造器中注入Router路由就抛出这个循环依赖的异常 解决方式:使用injector ......
  • golang依赖注入工具digo
    golang依赖注入工具digo工具地址:https://github.com/werbenhu/digo特性使用注释中的注解自动代码生成自动检测循环依赖编译时期依赖注入自动初始化支持实例组的管理快速开始更多示例请参考:examples编写代码和注解packagemainimport( "log" "github.com/werbe......
  • gorm查询时间去掉时区问题
    gorm查询的时间格式是2022-03-03T14:32:05+08:00,这种格式不符合我们日常使用习惯,尤其是前端展示会额外占用单元格宽度gorm不提供时间格式的配置,只能自定义个一个类型,继承time.Time,上代码,这个是网上一搜一堆的代码,没啥可以说的packageutilsimport("database/sql/drive......
  • go helloworld 部署到k8s
    打包容器shutdown_Dockerfile同级目录执行sudodockerbuild-thello:v0.01-fshutdown_Dockerfile. 导出docker容器AAA:8.2,8.2表示镜像版本号dockersave-otar名称.tarAAA:8.2BBB:5.6 推送到其它node节点[email protected]:/home/deployscp导......
  • 记一次go项目踩坑记录
    错误描述在go项目中创建了一个脚本,编写了一个main函数其中用到了一个modelgithub.com/link1st/go-stress-testing/stressimport("fmt"//"github.com/dji/go-stress-testing/stress" "github.com/link1st/go-stress-testing/stress""net/http&......
  • C温故补缺(十七):动态链接(ELF,PIC,GOT,PLT)
    动态链接(PIC,GOT,PLT,ELF)参考:51CTO通过静态链接,可以生成一个可执行文件,这个可执行文件既可以是完全链接的也可以是部分链接的,对于部分链接的可执行文件,有些符号引用需要等到可执行文件加载时甚至是运行时才会进行符号解析和重定位。动态链接与静态链接一样包括符号解析和重......
  • ‘dependencies.dependency.version‘ for mysql:mysql-connector-java:jar is missin
    项目使用的技术框架是SpringBoot,依赖管理工具是Maven,需要用到数据库所以引入了mysql-connector-java相关jar包。之前项目一直正常的,不会报错,最近更新了一下版本,项目启动的时候直接报错:[INFO]Scanningforprojects...[ERROR][ERROR]Someproblemswereencounteredwhilep......