共计 8590 个字符,预计需要花费 22 分钟才能阅读完成。
简介:IOC(inversion of control)即管制反转,是面向对象编程中的一种设计准则,能够用来减低计算机代码之间的耦合度。IOC-golang 是一款服务于 Go 语言开发者的依赖注入框架,基于管制反转思路,不便开发人员搭建任何 Go 利用。在本文中,我不会列举这个我的项目的种种性能与实现,而是站在开发者的角度,谈一谈我认为 Go 利用开发的“现实姿势”。
image.png
作者 | 李志信 (冀锋)
起源 | 阿里开发者公众号
IOC(inversion of control)即管制反转,是面向对象编程中的一种设计准则,能够用来减低计算机代码之间的耦合度。IOC-golang 是一款服务于 Go 语言开发者的依赖注入框架,基于管制反转思路,不便开发人员搭建任何 Go 利用。
在本文中,我不会列举这个我的项目的种种性能与实现,而是站在开发者的角度,谈一谈我认为 Go 利用开发的“现实姿势”。
我的项目背景
在面向对象编程的思路下,开发者须要间接关怀对象之间的依赖关系、对象的加载模型、对象的生命周期等等问题。对于较为简单的业务利用零碎,随着对象数目增长,对象之间的拓扑关系呈指数级减少,如果这些逻辑全副由开发人员手动设计和保护,将会在利用内保留较多业务无关的冗余代码,影响开发效率,进步代码学习老本,减少了模块之间的耦合度,容易产生循环依赖等等问题。
随着开发者的增多,设计模型的复杂化,将会产生对象治理框架的诉求,例如 Java 生态的 Spring 框架,其设计的外围就是管制反转思路,从而为开发者提供依赖注入、配置注入、生命周期治理等能力。Go 语言生态在开源侧也有较多基于该思路的实现,但广泛能力较为繁多,相比于咱们的设计思路,在可扩展性、易用性等方面有所有余。
IOC-golang 不是 Go 语言实现的 Spring 框架!
咱们致力于打造一款针对 Go 开发人员的框架,它适配与 Go 的语法和各种基本概念,合乎 Go 语言开发习惯,能真正为开发人员提供编程、思考、运维、以及代码浏览上的便当。
设计思路
让咱们聊一些轻松的话题。
利用开发思路
应用程序多种多样,都是由开发人员一行一行代码编写进去的,身为开发人员,在编写代码之前,肯定是对接下来要写的每一行代码有初步的思考与设计。例如,我身为一个 Go 开发人员,如果冀望编写一个 web 后端服务程序,那么我会怎么做?
最直观的思路,我须要启动一个 http server,用于监听某个端口,并且解决 http 协定的申请。应用面向对象的思路,我须要构建一个 http server 对象,之后调用办法开启监听。再往下一层思考,这个 http server 对象如果要创立进去,须要依赖一些对象,这些对象可能蕴含:多个 http handler 对象、用于可视化上报的对象、治理平安认证的对象等等。再下一层,一个 http handler 对象依赖的对象有:负责执行序列化操作的对象、传输构造对象、业务解决对象;业务解决对象又依赖一些 sdk,例如缓存客户端对象、数据库客户端对象等等。咱们当初层层思考的过程,也就是自顶向下的设计模型。
咱们能够把一个简单的应用程序,依据依赖关系,形象为一个具备单终点的有向图,以下面形容的场景为例,咱们能够画出具备如下拓扑的图。
image.png
脑海中有了这些拓扑,就能够依照习惯的形式编写代码了,我可能抉择先把未实现的模块形象成接口,由上至下编写构造,我也可能习惯自底向上开发,先从最具体的底层构造动手,而后用多个子结构组成一个残缺的下层构造。无论抉择哪种实现计划,我在开发时总会关怀一件事件:我要开发的构造,是由哪些构造组成的,我把这个事件称作一个“开发单元”,这也是 IOC-golang 框架关怀的次要问题之一。
依照惯例的利用开发模式,在一个“开发单元”内,开发者须要关注哪些事件?咱们习惯于编写一个构造函数返回须要的对象,这个构造函数的入参,蕴含了一些参数以及上游依赖,咱们在构造函数中会把这些对象和参数拼接成一个构造,再执行初始化逻辑,最初返回。
咱们把这个“开发单元”关怀的货色,依照依赖关系形象成下图。
image.png
也就是说,如果想基于一个构造结构出一个对象,咱们最多须要提供这三个货色就够了:参数 / 配置、依赖的子对象和一段蕴含初始化逻辑的函数,当然对于一些简略的构造,可能只须要三者中的一两者,甚至都不须要。依照这一思路,开发人员能够把“编写一个利用”,拆分成若干个“结构一个对象”的过程,二者是等价的,咱们都在编码的过程中,耳濡目染地做了这件事件。
IOC-golang 能够帮忙开发者更清晰地“结构一个对象”
在应用 IOC-golang 开发的过程中,开发者只须要将参数、依赖对象、初始化逻辑这三要素通过注解或标签的模式标注在代码中,即可实现一个构造的定义,齐全无需关怀构造的拼装过程和依赖对象的创立过程,让开发者专一于以后构造的业务逻辑。
// +ioc:autowire=true
// +ioc:autowire:type=normal
// +ioc:autowire:paramType=Config
// +ioc:autowire:constructFunc=New
type RedisClient struct {
client *redis.Client
ServiceImpl1 Service singleton:"main.ServiceImpl1"
// inject Service ‘s ServiceImpl1
}
type Config struct {
Address string
Password string
DB string
}
func (c Config) New(impl Impl) (*Impl, error) {
dbInt, err := strconv.Atoi(c.DB)
if err != nil {return impl, err}
client := redis.NewClient(&redis.Options{
Addr: c.Address,
Password: c.Password,
DB: dbInt,
})
_, err = client.Ping().Result()
if err != nil {return impl, err}
impl.client = client
return impl, nil
}
基于 IOC-golang 框架开发的,一个蕴含了 redis 客户端的构造 RedisClient,通过注解或标签指定了参数、初始化逻辑和依赖的对象。
如果依照惯例的开发方式,开发者还须要要额定做这些事件:手动从配置文件读取 Config 对象的所有字段,手动把 ServiceImpl1 对象创立进去,手动拼装 RedisClient 对象而后调用初始化逻辑。而这些通用的逻辑都被 ioc-golang 封装好了。
IOC-golang 能够帮忙开发者治理设计模型
对象如何被获取和注入,参数从哪里加载,接口由谁实现,以及下面提到的“是否单例模型”等等问题,只须要开发者在构造注解中标注好应用的主动装载模型,就能够达到冀望的成果。开发者也能够定制化须要的主动装载模型。
// +ioc:autowire=true
// +ioc:autowire:type=singleton
type App struct {
ServiceImpl1 Service `singleton:"main.ServiceImpl1"` // inject Service 's ServiceImpl1 implementation
RedisClientPtr *RedisClient `normal:",address=localhost:6379&db=0"` // inject RedisClient struct pointer
}
基于 IOC-golang 框架开发的,一个应用单例模式的对象 APP,其依赖一个 Service 接口,该接口冀望被 main.ServiceImpl1 单例模式构造注入;APP 还依赖一个 *RedisClient 构造体指针,冀望以多例模式注入,并传入了参数。参数也能够从配置文件的构造默认地位读取。
如果依照惯例的开发方式,开发者须要额定手动读取参数并创立依赖的 RedisClient 对象,手动组装 APP 对象,保护单例模型指针,提供单例模型的构造函数等,这些逻辑都被 IOC-golang 封装好了。
对象生命周期
上一节所说的是动态的编码过程,这一节咱们来聊一聊,一个对象从动态的编码,到利用运行过程中被加载直到销毁的整个生命周期。
一个 Go 对象的残缺生命周期个别蕴含以下几个环节:
构造定义:构造提供者编写构造的字段与函数。
参数传入与依赖对象加载:对象创立的筹备阶段,获取到依赖的参数,获取到所有依赖的上游对象。
对象创立与初始化:对象的组装过程,组装实现后执行必要的初始化逻辑。
对象应用:对象的函数被使用者调用
对象销毁:对象被销毁
两个视角:
上述对象的生命周期,是站在对象的角度来察看的。咱们还能够从构造开发者视角和构造使用者视角来察看。为了表述的更为形象,咱们能够用“产品说明书”来比喻构造的全副信息。
image.png
构造开发者视角:(撰写产品说明书)
我负责定义构造的字段和函数
我负责明确依赖的上游对象、依赖的参数字段。
我负责明确参数应该从哪里加载,例如:从标签加载,从配置中某个地位加载,从 API 传入的参数中加载。
我负责明确构造的依赖注入模型,比方单例模型。
我负责定义对象的初始化逻辑
我负责定义对象的销毁逻辑
构造使用者视角:(浏览产品说明书,依照说明书的内容应用构造)
我负责明确要应用哪个构造。(找到对应的产品说明书)
我负责应用一种“产品说明书”中反对的主动装载模型,例如:单例模型。
我负责明确“产品说明书”中给定的构造依赖参数。
我负责应用一种“产品说明书”里反对的参数加载形式,来加载构造依赖参数,加载形式可能包含:从标签加载,从配置中某个地位加载,从 API 传入的参数中加载。
我负责应用一种对象获取形式,来获取对象实例,例如:通过 API 获取,通过标签注入获取。
我负责调用对象函数。
我负责触发对象销毁逻辑。
上述的两个视角,是开发者在面向对象编程的开发过程中肯定会思考的。ioc-golang 框架在设计中明确了这两个视角。让一个构造的生命周期不再是一串面向过程的操作,而是两侧责任明确的开发模型。
可扩展性
IOC-golang 全面拥抱可扩展性,咱们心愿您在框架内接触到的任何概念都是可横向扩大的。可扩展性不意味着任何事件都要手动编写,而是在领有足够预置实现计划的根底之上,反对针对非凡场景的定制化。如果您的计划足够通用,也能够提交至开源侧,让更多人能够享受你的计划带来的便当。
IOC-golang 的可扩展性体现在多个方面,其中最重要的是依赖注入过程的可扩展性,这也是框架能的外围能力。
依赖注入的可扩展性蕴含三个维度,从具体到形象别离是:
对象的可扩展性
对象的可扩展性,即针对确定的一个构造(非单例),你能够通过传入不同的参数来获取多个冀望的对象。这个过程往往被构造使用者关注,他须要思考如何传入参数,取得对象,调用对象从而实现正确的业务逻辑。通过这一可扩展性,构造使用者能够扩大出多个对象实例。
构造的可扩展性
构造的可扩展性,即针对一个确定的主动装载模型,你能够通过定义本人的构造形容信息,将你的构造体注册在框架上,以供应用。这个过程是构造提供者关怀的,他须要思考选用哪个主动装载模型,思考提供的构造的全副生命周期信息。通过这一可扩展性,构造提供者能够为框架注入多种多样的构造,这些构造都会依照被抉择的主动装载模型执行加载逻辑。
框架提供了一些预置的构造,例如 redis 客户端、gorm 客户端等等,开发者能够间接传入参数,注入或通过 API 获取,间接应用,但这些预置的构造肯定无奈笼罩业务需要的。开发者注册本人的任何构造到框架上,都是应用了构造的可扩展性,这些被注册的构造和框架提供的预置构造,都是同一层面的概念。
主动装载模型的可扩展性
主动装载模型形容了一类构造的装载形式,例如是否单例模型、参数从哪里加载、注入标签应该合乎什么格局等等。这个过程也是构造提供者关怀的。
框架提供了一些预置的主动装载模型,例如单例模型、多例模型、配置模型、rpc 模型等,开发者能够依据依照业务须要,将一个或多个构造注册在冀望的主动装载模型上,当已有的主动装载模型不足以适配业务场景,开发者能够调用 API 进行定制化。
下图形容了框架可扩展性的架构
image.png
小结
IOC-golang 框架关注:
一个问题:一个对象如何被创立。
两个角度:构造开发者视角,构造使用者视角。
三个维度扩大:主动装载模型、构造、对象。
我的项目层级与性能
我的项目层级
IOC-golang 是一个语言绑定的框架,处于利用开发的最高层,间接由开发人员操作。一些开发罕用的组件所处层级如下图所示。这也解释了,IOC-golang 能够称为服务于 Go 开发者的 ioc 框架,而不是 Spring 框架的 Go 语言实现。
image.png
在软件架构的层级中,往往层级越高的模块越形象,其应用体验更佳,能力覆盖范围更广,对于同语言上层的模块扩展性更好,但会造成语言绑定。层级越低的模块越形象,其泛用性更佳,更具备通用型,更容易做到语言无关。
次要性能
次要性能有二,即 IOC 和 AOP,别离代表了面向开发的“依赖注入”能力,和面向运维的“代理构造 AOP 层”。
依赖注入能力:IOC
依赖注入能力
弱小、易用、可扩大的依赖注入能力,是框架的外围性能。
开发者能够将任何构造体注入至标签字段:
// +ioc:autowire=true
// +ioc:autowire:type=singleton
type App struct {
ServiceStruct *ServiceStruct `singleton:""` // inject ServiceStruct struct pointer
}
将任何构造体注入至任何接口:
// +ioc:autowire=true
// +ioc:autowire:type=singleton
type App struct {
ServiceImpl1 ServiceInterface `singleton:"main.ServiceImpl1"` // inject ServiceInterface 's ServiceImpl1 implementation
}
能够通过 API 的形式,获取任何已注册的构造体:
// 该函数由 iocli 工具主动生成
func GetApp() (*App, error) {
i, err := singleton.GetImpl(util.GetSDIDByStructPtr(new(App)), nil)
if err != nil {return nil, err}
impl := i.(*App)
return impl, nil
}
灵便的参数传递能力
本框架反对通过标签、API、配置三种形式传入依赖参数,并容许开发者扩大这一能力。
标签参数传递:传递 address, db 等构造所需参数
// +ioc:autowire=true
// +ioc:autowire:type=singleton
type App struct {
NormalDB3Redis normalRedis.ImplIOCInterface `normal:",address=127.0.0.1:6379&db=3"`
}
通过 API 进行参数传递:
func main() {
client := redis.NewClient(&redis.Options{Addr: p.RedisAddr,})
}
通过配置进行参数写入:ioc_golang.yaml, 配置 github.com/alibaba/ioc-golang/extension/normal/nacos.Impl 构造的结构参数
autowire:
normal:
github.com/alibaba/ioc-golang/extension/normal/nacos.Impl:
my-nacos:
param:
nacosclientparam:
clientconfig:
appKey: appKey
serverconfigs:
- ipaddr: 127.0.0.1
port: 8848
构造代理层:AOP
本框架提供的 iocli 命令行工具会辨认构造注解,从而生成注册代码、构造代理层、构造专属接口等信息,缩小开发人员须要编写的代码量。
咱们以这样一个构造为例:
// +ioc:autowire=true
// +ioc:autowire:type=singleton
type Impl1 struct {
}
func (i *Impl1) Hello(req string) string {
return req
}
开发者能够通过执行 iocli gen 命令,一键为工程内所有标注的构造生成代码,生成位于构造雷同目录的 zz_generated.ioc.go,其内容包含:
构造注册代码,会以构造描述符的模式,将构造生命周期注册到框架上。
func init() {
singleton.RegisterStructDescriptor(&autowire.StructDescriptor{Factory: func() interface{} {return &Impl1{}
},
})
}
构造专属接口,命名为 $(构造名)IOCInterface
type Impl1IOCInterface interface {
Hello(req string) string
}
构造代理层存根
框架会为所有构造提供代理层,在面向接口编程的场景下,所有申请都会通过代理层,扩大出弱小的运维能力。
type impl1_ struct {
Hello_ func(req string) string
}
func (i *impl1_) Hello(req string) string {
return i.Hello_(req)
}
构造获取 API
// 获取构造指针
func GetImpl1() (*Impl1, error) {
i, err := singleton.GetImpl(util.GetSDIDByStructPtr(new(Impl1)), nil)
if err != nil {return nil, err}
impl := i.(*Impl1)
return impl, nil
}
// 获取包装了代理层的专属接口
func GetImpl1IOCInterface() (Impl1IOCInterface, error) {
i, err := singleton.GetImplWithProxy(util.GetSDIDByStructPtr(new(Impl1)), nil)
if err != nil {return nil, err}
impl := i.(Impl1IOCInterface)
return impl, nil
}
以上代码均由工具主动生成,开发者只需应用即可。通过这些代码展现,我想传播给读者一个信息,就是 IOC-golang 能够治理任何对象的代理存根,这也就意味着,咱们能够用这一层代理做任何想做的事件。其中就包含框架曾经实现的能力:
构造展现:能够展现所有加载到框架的构造、办法列表
接口参数动静监听:能够监听运行态的 go 过程,展现以后正在执行的函数、参数值、返回值。
链路追踪:能够获取一个申请的残缺调用链路,获取分布式场景下的跨过程调用链路,从而剖析性能瓶颈。
但这些只是 ” 运维 ” 这个宏大的话题的冰山一角,咱们基于构造代理 AOP 层,能够做任何咱们能想到的事件:
链路可视化、分布式应用拓扑展现
告警
故障注入
问题诊断
…
这能够由咱们一起设计,一起实现,一起见证!
丰盛的组件
IOC-golang 目前已预置一系列开发罕用 sdk,供间接注入。笼罩数据库、缓存、中间件、RPC 等等畛域。目前已反对的组件有:
Nacos
Rocketmq
Redis
gRPC
GORM
Dubbo3
HttpServer
在将来将会反对更丰盛的罕用 Go 开发 SDK,为开发者提供全家桶式的开发体验。
上述只是简略的介绍,更具体的内容请参考文末提供的我的项目示例。具体模块的更具体设计与介绍,例如基于 IOC 思路的 RPC,分布式全接口链路追踪能力,将在后续文章中与大家见面,敬请期待。
次要性能次要性能有二,即 IOC 和 AOP,别离代表了面向开发的“依赖注入”能力,和面向运维的“代理构造 AOP 层”。
愿景
IOC-golang 是一个簇新的我的项目,从第一行代码的编写,到明天不过一个月的工夫。
咱们的愿景是让 IOC-golang 成为 Go 利用开发的首选框架,让 Go 开发人员更贴近业务逻辑,缩小冗余代码,减少代码的易读性,让 Go 生态的“面向对象编程”进入自动挡时代。
次要性能次要性能有二,即 IOC 和 AOP,别离代表了面向开发的“依赖注入”能力,和面向运维的“代理构造 AOP 层”。
如何奉献
我的项目初创,非常期待能与感兴趣的开发者共创一片天地。
我的项目 github 地址:github.com/alibaba/ioc-golang
我的项目文档:ioc-golang.github.io
我的项目示例:ioc-golang/example
欢送退出钉钉群“IOC-golang 交换群”:44638289
云原生环境下基于 Serverless 架构的翻新利用
点击这里,查看详情。
原文链接:http://click.aliyun.com/m/100…
本文为阿里云原创内容,未经容许不得转载。