正交性最为最核心的 Koishi 开发哲学,是许多其他设计模式的基础。
一、定义
正交性并非 Koishi 提出,而是早已普遍应用的软件开发范式。在 Koishi 开发哲学中,正交性特指一切能够被复用的行为都应该被插件独立控制。与传统解释的区别在于,正交的 Koishi 并非实现范式而是设计范式,其目的是赋予用户最大的自主权和创造性,同时确保插件生态的一致性和可逆性。
二、设计动机
实现了「正交的 Koishi」的项目会拥有以下优点:
2.1 细粒度控制
即便一个插件没有提供任何配置项,用户也能控制插件的大量行为,包括但不限于权限管理、速率控制、文本替换、上下文过滤等。
2.2 一致性
用户得以通过统一的页面配置不同插件的行为,降低了学习成本和心智负担。
2.3 可逆性
正交性可以让各项功能得以有效解耦,从而允许服务开发者设计出自回收的 API。
三、生态现状
目前的 Koishi 生态普遍依赖此模式。
3.1 控制台页面
Koishi 官方的控制台插件提供的不同页面恰好管理了不同的功能:
- 指令管理:给指令更名、权限管理、速率控制、调整层级关系
- 本地化:覆盖任意输出文本
- 插件管理:提供统一的配置界面
- 数据库:查看和编辑数据库
- 下载管理:管理插件的下载资源 (WIP)
一个插件可能同时访问多种服务所提供的资源,而用户在访问控制台时,既可以基于插件又可以基于服务进行配置。两者之间还通过跳转按钮建立了联系。
3.2 抽象服务
Koishi 依照正交性原则设计了许多抽象服务,即某个功能同时被多个插件实现:
- database:数据库服务
- assets:资源管理服务
- cache:缓存服务
- translator:翻译服务
- censor:内容审查服务
3.3 大型插件的拆分
许多大型插件也依照正交性原则进行了有效的拆分:
- koishi-plugin-dialogue
- koishi-plugin-nonebot
四、实现原理
Koishi 基于 Cordis 开发,后者作为 AOP 框架,提供了多种方式实现正交性。尽管这已经是软工领域熟知的话题,但具体到 Koishi 的实现上还是有一些值得介绍的地方。
4.1 模块扩张与声明合并
Koishi 是少有的不通过 DI 实现 AOP 的框架。得益于 TypeScript 优秀的 模块扩张 (Module Augmentation) 与 声明合并 (Declaration Merging) 机制,Koishi 得以实现服务类型的自动导入,并通过更多的服务践行正交化的开发。
4.2 事件和插件系统
事件和插件是 AOP 的传统开发范式。Koishi 扩展了 Node.js 原生的 EventEmitter,既实现了更丰富的触发形式,又为所有的 API 确保了可逆性。在插件系统方面,Koishi 按依赖关系决定插件的加载顺序,解决了服务类插件面临的难题。
4.2 配置混入
在轻量级和贴近用户的场景下,部分功能也会使用配置混入的方式实现正交化。这其中最普遍的是对指令配置的使用。
五、对比
5.1 正交与可逆
为了确保服务 API 的可逆性,通常需要对服务进行正交化设计。
5.2 正交与其他特性
正交性设计将大量代码从插件转移到服务中,使得插件的可迁移性增加。只要从少数服务入手就能实现 Koishi 的零占用、零状态和 0dt。