Malagu 框架的设计核心是组件,组件可以将一件特别复杂多样的事,分解成一个个相对独立且抽象的组件,通过组件的不同编排解决各种场景业务需求。组件的扩展性是保证组件灵活性、通用性的基本能力。
扩展能力来源
- 组件属性优先级规则
- 组件中的对象托管给容器(IOC)
组件属性优先级规则
组件之间可以存在组件依赖,我们可以依赖某个更为基础的组件实现自己的子组件,子组件的组件属性优先级大于父组件,我们通过组件属性的同名覆盖,覆盖掉父组件的某个组件属性配置,从而达到扩展父组件的目的。比如组件 @malagu/web
的组件属性 malagu.server.port
为 3000,我们在子组件中通过如下配置既可以覆盖父组件的该组件属性:
malagu:
server:
port: 8080
组件中的对象托管给容器(IOC)
Malagu 框架的核心基础设施是 IoC 容器,组件把所有实现业务功能的对象都统一托管给容器。容器中的的对象绝大多数都是单例,减少不必要的内存;通过延迟按需初始化对象,加快应用启动速度;对象依赖使用装饰器声明式注入,对象依赖管理变得更为简单。这些都是 IoC 容器天然就有的优势。
每个托管到容器的对象都会有一个 ID 标识。ID 标识,一方面,方便我们声明需要注入的是哪个依赖对象,另一方面,方便我们通过 ID 替换一个旧的实现或者追加一个新的实现。
替换旧的实现
Malagu 推荐面向对象编程,面向对象编程的一个原则是面向抽象编程。Malagu 自身的组件都是面向抽象编程原则的。Malagu 实现一个功能,首先,抽象该功能需要的接口;然后,实现该接口,在实现的代码中只面向接口编程;最后,将实现的对象以接口作为 ID,将对象托管到容器中。如果我们需要替换掉该接口的实现,我们只需要给该接口 ID 重新绑定一个新对象即可。比如我们想替换掉 Session 存储器的默认实现,代码如下:
@Component({ id: SessionStore, rebind: true })
export class RedisSessionStore implements SessionStore {
.....
}
在 TS 中,接口定义在运行时,会被擦除,接口不会有真正的实体,所以接口是没办法作为 ID 标识的。为了能让接口看起像有实体。我们在声明接口的时候,同时再声明一个与接口同名的实体变量。
export const SessionStore = Symbol('SessionStore'); // 让接口有实体
export interface SessionStore {
get(id: string): Promise<Session | undefined>;
set(session: Session): Promise<void>;
remove(id: string): Promise<void>;
}
追加新的实现
在 IoC 容器中,当同一个 ID 绑定了多个对象,且我们通过该 ID 注入依赖的时候,就需要使用数组类型属性。
@Component()
export class Demo {
@Autowired(Middleware)
protected readonly middlewares: Middleware[];
......
}
Malagu 框架很多地方的实现都采用上面的技巧,把本来需要很多个 ifelse
才能完成的功能,拆解成统一接口的许多实现,最后这些实现都以同一个接口为 ID 托管到容器中。这种实现模式,可以将冗长、逻辑关系复杂的代码实现拆解成功能单一的实现,代码可读性更高,也便于使用同一接口追加新的接口实现。追加新的接口实现,只需要按照同一个接口作为 ID 托管到容器即可。比如我们可以追加一个新的中间件实现,代码如下:
@Component(Middleware)
export class SessionMiddleware implements Middleware {
......
}