按照面向接口编程的理念,将每个模块看成是一个服务,服务的具体实现我们其实并不关心,我们关心的是服务提供的能力,即接口协议。那么框架主体真正要做的事情是什么呢?其实是:定义好每个模块服务的接口协议,规范服务与服务之间的调用,并且管理每个服务的具体实现。
所有的服务都去框架主体中注册自身的模块接口协议,其他的服务调用功能模块的时候,并不是直接去这个服务获取实例,而是从框架主体中获取有这个接口协议的服务实例。
这样,所有的模块服务都不和具体的服务进行交互,而是和框架主体进行交互,所有的接口也都注册在框架主体中,非常方便管理。
每个模块服务都做两件事情:一是它和自己提供的接口协议做绑定,这样当其他人要使用这个接口协议时能找到自己;二是它使用到其他接口协议的时候,去框架主体中寻找。
所以,这个时候,每个模块服务都是一个“服务提供者”(service provider),而我们主体框架需要承担起来的角色叫做“服务容器”(service container),服务容器中绑定了多个接口协议,每个接口协议都由一个服务提供者提供服务。
在框架初始化启动的时候,我们可以选择在服务容器中绑定多个服务提供者,每个服务提供者对应一个凭证。当要使用到某个服务的时候,再根据这个凭证去服务容器中,获取这个服务提供者提供的服务。这样就能很方便地获取服务了。
这两个结构的逻辑非常重要,这里我再强调一下。我们的设计是将每个服务,不管是配置、还是日志、还是缓存,都看成是一个服务。
这个服务,通过提供一个服务提供者注册到服务容器中。服务提供者提供的是“创建服务实例的方法”,服务容器提供的是“实例化服务的方法”。至于这个服务实例拥有哪些能力,即符合哪个接口协议,是预先在框架主体中定义好的。
服务器提供者接口定义
个服务提供者需要有哪些能力呢?一共有五个能力:
- 获取到服务凭证的能力Name
- 创建服务实例化方法的能力Register
- 获取服务实例化方法参数的能力Params
- 两个与实例化控制相关的方法:控制实例化时机IsDefer、实例化预处理Boot
将此接口放在framework/provider.go中。
基本定义
每个服务提供者有一个凭证,用来和容器做绑定,则它有一个获取自身凭证的方法Name:
// Name 代表了这个服务提供者的凭证
Name() string
然后它有一个创建服务实例的方法,在绑定后,如果容易要初始化一个服务提供者实例,就会调用创建实例的方法。
按照面向接口编程的思想,每个具体服务“创建服务实例”的方法不一样,比如日志服务初始化的时候可能需要有日志输出地址,但是配置服务初始化的时候需要有配置文件地址,但是我们这里需要规范它们的输入和输出,使用 Golang 中的 function type,也叫函数定义,是可以做这个事情的。
// NewInstance 定义了如何创建一个新实例,所有服务容器的创建服务
type NewInstance func(...interface{}) (interface{}, error)
这个 NewInstance 就是一个函数定义,它规定所有创建服务实例的方法必须:有相同的参数 interface{} 数组,并且返回 interface{}和错误信息这两个数据结构。
- interface{} 数组代表实例化一个服务所需要的参数,我们这里设计为可变参数,为的是适配不同数量、不同类型的参数需求;
- 返回值返回的 interface{} 结构代表了具体的服务实例。
定义好了“创建服务实例的方法”的函数,我们再看服务提供者的创建能力如何实现,也就是 NewInstance 方法,它的返回值就是刚才写的 NewInstance 的函数定义。
// Register 在服务容器中注册了一个实例化服务的方法,是否在注册的时候就实例化这个服务,需要参考 IsDefer 接口。
Register(Container) NewInstance
而对于方法的输入参数,将服务容器传进来是因为,如果后续希望根据一个服务的某个能力,比如配置服务的获取某个配置的能力,返回定义好的不同 NewInstance 函数,那我们就需要先从服务容器中获取配置服务,才能判断返回哪个 NewInstance。
“创建服务实例的方法”的能力,除了实现 NewInstance 方法之外,还需要注册 NewInstance 方法的参数,即可变的 interface{}参数。所以我们的服务提供者还需要提供一个获取服务参数的能力。
// Params params 定义传递给 NewInstance 的参数,可以自定义多个,建议将 container 作为第一个参数Params(Container) []interface{}
Param(Container) []interface{}
实例化过程的控制
到这里服务提供者的能力已经基本设计好了。不过我们可以再思考下实例化的过程,看看还有没有什么讲究。
- 实例化的时机,可以在服务提供者注册的时候,也可以是第一次获取服务的时候,即是注册的时候就实例化,还是延迟到获取服务的时候实例化。
所以我们需要有一个能力能控制实例化的时机,对应到服务提供者上,要提供告知服务容器是否延迟实例化的方法 IsDefer。同样在 framework/provider.go 中。
// IsDefer 决定是否在注册的时候实例化这个服务,如果不是注册的时候实例化,那就是在第一次 make 的时候进行实例化操作
// false 表示不需要延迟实例化,在注册的时候就实例化。true 表示延迟实例化
IsDefer() bool
- 实例化之前有可能需要做一些准备工作,比如在每次实例化之前,想记录一下日志,或者想通过确认某些配置,修改一下实例化参数。
所以这里我们需要设计一个在实例化前调用准备工作的函数 Boot。它的参数是服务容器,返回值是一个 error,在实例化服务的时候,如果准备工作 Boot 失败了,那么我们就不进行后续的实例化操作了,将这个 error 直接返回给获取服务的方法。
// Boot 在调用实例化服务的时候会调用,可以把一些准备工作:基础配置,初始化参数的操作放在这个里面。
// 如果 Boot 返回 error,整个服务实例化就会实例化失败,返回错误
Boot(Container) error
这样, framework/provider.go 中完整代码如下:
package framework
// NewInstance 定义了如何创建一个新实例,所有服务容器的创建服务
type NewInstance func(...interface{}) (interface{}, error){}
// ServiceProvider 定义一个服务提供者需要实现的接口
type ServiceProvider interface {
// Register 在服务容器中注册了一个实例化服务的方法,是否在注册的时候就实例化这个服务,需要参考 IsDefer 接口。
Register(Container) NewInstance
// Boot 在调用实例化服务的时候会调用,可以把一些准备工作:基础配置,初始化参数的操作放在这个里面。
// 如果 Boot 返回 error,整个服务实例化就会实例化失败,返回错误
Boot(Container) error
// IsDefer 决定是否在注册的时候实例化这个服务,如果不是注册的时候实例化,那就是在第一次 make 的时候进行实例化操作
// false 表示不需要延迟实例化,在注册的时候就实例化。true 表示延迟实例化
IsDefer() bool
// Params params 定义传递给 NewInstance 的参数,可以自定义多个,建议将 container 作为第一个参数
Params(Container) []interface{}
// Name 代表了这个服务提供者的凭证
Name() string
}