.

.

IOC-控制反转

IOC 即控制反转,旨在降低代码的耦合度,减少冗余重复的对象组装代码,依赖注入是 IOC 思路的一种具体实现。

本框架命名为 IOC-golang, 是服务于 Go 开发者的 IOC 框架,使用语言原生概念,符合语言开发习惯。框架衍生出的多种能力和可扩展性,其能力建立在框架设计的 IOC 模型基础之上。

1 - 依赖注入

概念

依赖注入(Dependency Injection,DI) 是指开发人员无需显式地创建对象,而是通过特定标注(在本框架中为 标签 )的方式声明字段,由框架在对象加载阶段,将实例化对象注入该字段,以供后续使用。

优点

依赖注入可以降低代码的耦合度,减少冗余代码量,优化代码逻辑,使得开发人员可以只关心业务逻辑,帮助面向对象编程,面向接口编程。

IOC-golang 的依赖注入能力

本框架从两个角度实现依赖注入:

  • 结构如何被提供

    开发人员需要准备好需要被注入的结构,将它注册到框架。注册到框架的代码可以由开发人员手动编写,开发人员也可以通过使用注解标记结构,使用 iocli 的代码生成能力自动生成注册代码,从而减少工作量。

    一个带有 注解 的结构

    // +ioc:autowire=true
    // +ioc:autowire:type=singleton
    
    type App struct {}
    

    由 iocli 工具生成,或用户手动编写的注册代码。摘自 example/helloworld/zz_generated.ioc.go#L21

    func init() {
    	singleton.RegisterStructDescriptor(&autowire.StructDescriptor{
    		Factory: func() interface{} {
    			return &App{}
    		},
    	})
    }
    
  • 结构如何被使用

    • 通过标签注入

      开发人员需要通过标签来标记需要注入的结构字段。

      需要通过标签注入依赖至字段的结构,其本身必须也注册在框架上。

      // +ioc:autowire=true
      // +ioc:autowire:type=singleton
      
      type App struct {
      	MySubService Service `singleton:"main.ServiceImpl1"` 
      }
      
    • 通过 API 可获取对象,入参为 结构 ID (结构描述 ID,SDID) 和构造参数(如需要)。

      通过 API 获取对象的过程会默认通过代码生成。参考 example/helloworld/zz_generated.ioc.go#L190

      appInterface, err := singleton.GetImpl("main.App")
      if err != nil {
        panic(err)
      }
      app := appInterface.(*App)
      
      redisInterface, err := normal.GetImpl("github.com/alibaba/ioc-golang/extension/normal/redis.Impl", &Config{
          Address: "localhost:6379"
      })
      if err != nil {
        panic(err)
      }
      redisClient := redisInterface.(*Impl)
      

2 - 自动装载模型

概念

自动装载(Autowire)模型 封装了一类对象的装载方式,是本框架对装载过程抽象出的概念和接口。

用户需要定义的结构千变万化,如果每个结构描述符都要提供完整的装载过程信息,将是一件很麻烦的事情。我们将一类相似的结构抽象出一个 自动装载模型,选择注册在该模型的所有结构都需要遵循该模型的加载策略,这大大降低了用户需要提供的 结构描述符 内的定制化信息量,从而提高开发效率。

框架内置了两个基础自动装载模型:单例模型(singleton)多例模型(normal)

当前版本中,框架内置了三个扩展的自动装载模型:配置(config),gRPC 客户端(grpc),RPC(rpc)。其中配置模型是多例模型的扩展,gRPC 客户端是单例模型的扩展,RPC 模型提供了(rpc-client 和 rpc-server)两侧的自动装载模型。关于这三个自动装载模型的应用,可以参考example/autowire example/third_party/grpc 中给出的例子。

基于这些自动装载模型,框架内置了基于“扩展自动装载模型”的多个结构。例如,用户可以用几行代码将 “gRPC 客户端存根”注册在 “grpc 装载模型” 之上【示例】,再例如可以方便地从配置文件中的 指定位置读入 数据。

3 - 结构描述符

概念

结构描述符(Struct Descriptor, SD) 用于描述一个被开发者定义的结构,包含对象生命周期的全部信息,例如结构类型是什么,依赖哪些参数,如何被构造等等信息。

SD可以通过 注解 的方式使用工具 自动生成 。但还是推荐开发人员了解本框架定义的结构生命周期和结构描述信息,以便更清晰地了解加载过程。

对象生命周期

开发人员在 Go 语言开发过程中需要时时关注对象的生命周期,一个常见的对象生命周期如下:

  1. 对象定义:开发人员编码,编写结构,实现接口,确定模型(单例、多例..) 。
  2. 加载全部依赖:依赖的下游对象创建、配置读入等。
  3. 对象创建:产生一个基于该对象的指针
  4. 对象构造:获取全部依赖,并将其组装到空对象中,产生可用对象。
  5. 对象使用:调用对象的方法,读写对象字段。
  6. 对象销毁:销毁对象,销毁无需再使用的依赖对象。

参数

本框架的“参数”概念,是一个结构体,该结构体包含了创建一个对象所需全部依赖,并提供了构造方法。

例如:

type Config struct {
	Host      string
	Port      string
	Username  string
	Password  string
}

func (c *Config) New(mysqlImpl *Impl) (*Impl, error) {
	var err error
	mysqlImpl.db, err = gorm.Open(mysql.Open(getMysqlLinkStr(c)), &gorm.Config{})
	mysqlImpl.tableName = c.TableName
	return mysqlImpl, err
}

Config 结构即为 Impl 结构的“参数”。其包含了产生 Impl 结构的全部信息。

结构描述符 (Struct Descriptor)

定义的结构描述符如下:摘自 autowire/model.go

type StructDescriptor struct {
	Factory       func() interface{} // raw struct
	ParamFactory  func() interface{}
	ParamLoader   ParamLoader
	ConstructFunc func(impl interface{}, param interface{}) (interface{}, error) 
	DestroyFunc   func(impl interface{})
	Alias         string // alias of SDID
	TransactionMethodsMap map[string]string // transaction

	impledStructPtr interface{} // impledStructPtr is only used to get name
}
  • Factory【必要】

    结构的工厂函数,返回值是未经初始化的空结构指针,例如

    func () interface{}{
    	return &App{}
    }
    
  • ParamFactory【非必要】

    参数的工厂函数,返回值是未经初始化的空参数指针,例如

    func () interface{}{
    	return &Config{}
    }
    
  • ParamLoader【非必要】

    参数加载器定义了参数的各个字段如何被加载,是从注入标签传入、还是从配置读入、或是以一些定制化的方式。

    框架提供了默认的参数加载器,详情参阅 参数加载器概念

  • Constructor【非必要】

    构造函数定义了对象被组装的过程。

    入参为对象指针和参数指针,其中对象指针的所有依赖标签都已被注入下游对象,可以在构造函数中调用下游对象。参数指针的所有字段都已经按照参数加载器的要求加载好。构造函数只负责拼装。

    返回值为经过拼装后的指针。例如:

    func(i interface{}, p interface{}) (interface{}, error) {
      param := p.(*Config)
      impl := i.(*Impl)
      return param.New(impl)
    },
    
  • DestroyFunc 【非必要】

    定义了对象的销毁过程,入参为对象指针。

  • Alias 【非必要】

    由于 结构ID 一般较长,可以在这里指定结构的别名,可以通过这一别名替换 结构ID,调用 对象获取 API

  • TransactionMethodsMap 【非必要】

    基于 Saga 模型的事务函数声明, 这一 Map 的 Key 为需要使用事务能力的方法名,Value 为该方法的回滚函数,如果 Value 为空,则无回滚逻辑。参考事务例子 example/transaction

结构ID

  • 定义

结构(描述) ID定义为:"${包名}.${结构名}"

结构(描述) ID (Struct Description Identification) 在本文档和项目中多处被缩写为 SDID。

SDID 是唯一的,用于索引结构的键,类型为字符串。

  • 使用

例如,开发人员在 使用 API 获取对象 时,需要针对使用的自动装载模型,传入 SDID 来定位结构,从而获取对象。

4 - 参数加载器

概念

参数加载器描述了依赖参数如何在对象构造之前被加载,包括但不限于从配置加载、从 标签 参数加载等等。

参数加载器作为 SD(结构描述符)的一部分,可以被结构提供方定制化,也可以使用自动装载模型提供的默认参数加载器。

默认参数加载器

任何 SD 内定义参数加载器均被优先执行,如果加载失败,则尝试使用默认参数加载器加载。

默认参数加载器被两个基础自动装载模型(singleton、normal)引入。依次采用三种方式加载参数,如果三种方式均加载失败,则抛出错误。

  • 方式1 从标签指向的对象名加载参数,参考 配置文件结构规范

    Load support load struct described like:
    ```go
    normal.RegisterStructDescriptor(&autowire.StructDescriptor{
    		Factory:   func() interface{}{
    			return &Impl{}
    		},
    		ParamFactory: func() interface{}{
    			return &Config{}
    		},
    		ConstructFunc: func(i interface{}, p interface{}) (interface{}, error) {
    			return i, nil
    		},
    	})
    }
    
    type Config struct {
    	Address  string
    	Password string
    	DB       string
    }
    
    ```
    with
    Autowire type 'normal'
    StructName 'Impl'
    Field:
    	MyRedis Redis `normal:"github.com/alibaba/ioc-golang/extension/normal/redis.Impl, redis-1"`
    
    from:
    
    ```yaml
    extension:
      normal:
        github.com/alibaba/ioc-golang/extension/normal/redis.Impl:
          redis-1:
            param:
              address: 127.0.0.1
              password: xxx
              db: 0
    ```
    
  • 方式2 从标签加载参数

    Load support load param like:
    ```go
    type Config struct {
    	Address  string
    	Password string
    	DB       string
    }
    ```
    
    from field:
    
    ```go
    NormalRedis  normalRedis.Redis  `normal:"github.com/alibaba/ioc-golang/extension/normal/redis.Impl,address=127.0.0.1&password=xxx&db=0"`
    ```
    
  • 方式3 从配置加载参数,参考 配置文件结构规范

    Load support load struct described like:
    ```go
    normal.RegisterStructDescriptor(&autowire.StructDescriptor{
    		Factory:   func() interface{}{
    			return &Impl{}
    		},
    		ParamFactory: func() interface{}{
    			return &Config{}
    		},
    		ConstructFunc: func(i interface{}, p interface{}) (interface{}, error) {
    			return i, nil
    		},
    	})
    }
    
    type Config struct {
    	Address  string
    	Password string
    	DB       string
    }
    ```
    with
    Autowire type 'normal'
    StructName 'Impl'
    
    from:
    
    ```yaml
    autowire:
      normal:
        github.com/alibaba/ioc-golang/extension/normal/redis.Impl:
          param:
            address: 127.0.0.1
            password: xxx
            db: 0
    ```
    

5 - 注解

概念

注解(annotation) 是 Go 代码中符合特定格式的注释,一般位于结构定义之前。可被命令行工具代码扫描识别,从而获取结构信息,自动生成需要的代码。 例如 快速开始 示例中的注解:

// +ioc:autowire=true
// +ioc:autowire:type=singleton

type App struct {
	...
}

注解在 Go 应用开发的过程中,是一种直观、清晰的结构描述方式,通过使用注解进行标注,使用工具自动生成相关代码,可减少开发人员的工作量,降低代码重复率。

注解与代码生成

注解与代码生成能力,是为了让开发者无需关心 SD(结构描述符) 的组装和注册过程。开发者只需定义好结构,正确标注注解,即可使用 iocli 工 具自动生成当前目录和子目录下的 SD ,从而将编写的结构交给框架管理。

iocli 工具支持的注解

详情参阅 iocli #注解与代码生成