未来改进方向
本文档记录了参考 InversifyJS、TypeDI、NestJS、Spring 等成熟框架经验,对 @kaokei/di 项目提出的潜在改进思路。每个改进点下方的「作者态度」章节用于记录作者对该改进的看法。
1. 作用域(Scope)扩展
当前只支持单例模式,这在很多场景下是不够的。
- 瞬态作用域(Transient):每次
get()都创建新实例,适用于无状态的工具类、策略对象等 - 请求作用域(Request Scope):在一次"请求"上下文中共享同一实例,适用于 HTTP 请求处理链、事务上下文等。NestJS 的
Scope.REQUEST就是典型案例 - 实现思路:可以在 Binding 上增加
inTransientScope()/inRequestScope()链式调用,内部通过 status 和 cache 策略区分。Request Scope 需要引入一个 Context 对象来追踪当前请求
作者态度
可以考虑增加“瞬态作用域”。不考虑“请求作用域”,因为会增加不必要的“复杂度”。
2. 异步解析支持(Async Resolution)
当前 container.get() 是同步的,但 @PostConstruct 支持异步。这导致一个尴尬的局面:调用者拿到实例后,可能 PostConstruct 还没执行完。
- 提供
container.getAsync(token): Promise<T>方法,等待整个初始化链(包括异步 PostConstruct)完成后再返回 - 参考 InversifyJS v7 的
getAsync设计,以及 NestJS 中onModuleInit的异步等待机制 - 这样调用者可以确信拿到的实例是"完全就绪"的
作者态度
可以考虑增加getAsync这个API。
3. 多重绑定(Multi-Binding)
当前一个 token 只能绑定一个服务,重复绑定会抛异常。
- 支持
container.getAll(token): T[],获取同一 token 下的所有绑定实例 - 典型场景:插件系统、中间件链、事件处理器集合。比如 Spring 中
@Autowired List<Strategy>就是多重注入 - 可以考虑引入
bindMulti或bind().allowMultiple()来区分单绑定和多绑定
作者态度
暂时不考虑。如果是有限的多实例,可以通过创建多个不同的token来绑定同一个服务,也能达到获取多个实例的效果。
4. 条件绑定 / 命名绑定
- 支持
container.bind(token).toSelf().whenTargetNamed('mysql')这样的条件绑定 - 或者更轻量的方案:支持
Token带泛型标签,比如new Token<Database>('mysql-db')和new Token<Database>('pg-db')作为不同的 token - 当前其实已经可以通过不同的 Token 实例来区分,但缺少一个更优雅的"同接口多实现"的模式
作者态度
暂时不考虑。可以创建多个token来实现类似效果。
5. Container Module / 模块化注册
当前所有绑定都是手动逐个 container.bind() 的,当项目规模变大时会很繁琐。
- 参考 InversifyJS 的
ContainerModule,支持将一组相关绑定打包成模块 - 参考 NestJS 的
@Module概念,声明式地组织 providers / imports / exports - 简单实现:
container.load(module)接受一个回调函数(bind) => { bind(A).toSelf(); bind(B).toSelf(); } - 进阶:支持
container.unload(module)批量卸载
作者态度
暂时不考虑。
6. 自动绑定(Auto-Binding)
当前必须手动 bind(A).toSelf(),即使 token 就是类本身。
- 参考 TypeDI 的
Container.get(MyClass)自动注册行为:如果 token 是一个类且未绑定,自动执行bind(token).toSelf() - 参考 Spring 的组件扫描(Component Scan)思路,通过
@Injectable装饰器自动收集需要注册的类 - 可以提供一个
container.autoBindSelf = true的选项,或者container.get(token, { autoBind: true })
作者态度
暂时不考虑。更加倾向于明确的绑定关系,虽然自动绑定会比较方便,但会导致绑定关系不够明显。
7. 工厂绑定(Factory Binding)
当前 toDynamicValue 可以实现工厂模式,但语义不够明确。
- 提供
toFactory()方法,返回的不是实例而是工厂函数,调用者通过工厂函数按需创建实例 - 典型场景:需要根据运行时参数创建不同配置的实例
- 参考 InversifyJS 的
toFactory和toAutoFactory
作者态度
暂时不考虑。
8. 中间件 / 拦截器
- 参考 InversifyJS 的
applyMiddleware,在get()调用链中插入自定义逻辑 - 典型用途:日志记录、性能监控、权限检查、AOP 切面
- 比当前的
onActivation更灵活,因为中间件可以拦截每次get()调用,而不仅仅是首次激活
作者态度
暂时不考虑。
9. 容器快照(Container Snapshot)
- 参考 InversifyJS 的
container.snapshot()/container.restore() - 在测试场景中非常有用:测试前保存容器状态,测试后恢复,避免测试间的状态污染
- 实现思路:序列化
_bindingsMap 的状态,restore 时恢复
作者态度
暂时不考虑。
10. 类型安全增强
当前的类型推导已经不错,但还有提升空间:
container.get()在使用Token<T>时能正确推导类型,但toDynamicValue的返回类型校验可以更严格- 考虑引入
StrictContainer模式,在编译期检查所有 token 是否已绑定 - 参考 TypeDI 的类型推导方式,或者
typed-inject的编译期 DI 检查
作者态度
暂时不考虑。
11. 调试与诊断工具
- 提供
container.dump()或container.inspect()方法,输出当前容器的绑定关系图 - 提供依赖关系可视化:哪些服务依赖哪些服务,形成依赖图
- 循环依赖的错误信息可以更友好,展示完整的依赖链路径
- 参考 NestJS 的
DependenciesScanner和 Angular 的依赖注入调试工具
作者态度
暂时不考虑。
12. 事件系统
当前只有 onActivation / onDeactivation 两个生命周期钩子。
- 扩展为更完整的事件系统:
onBind、onUnbind、onResolve(每次 get 时)、onChildCreated、onDestroy - 支持多个监听器(当前 Container 级别只支持一个 handler)
- 参考 Spring 的
ApplicationEvent机制
作者态度
暂时不考虑。
13. 构造函数参数注入的替代方案
Stage 3 装饰器不支持参数装饰器,但可以考虑其他方式:
- 参考 Angular 的
inject()函数式 API:在构造函数体内调用this.service = inject(MyService)来获取依赖 - 这需要一个"当前解析上下文"的概念(类似 Angular 的 injection context),在实例化过程中维护一个全局/线程局部的上下文栈
- 好处是不需要参数装饰器,也能实现构造函数内的依赖获取
作者态度
暂时不考虑。本项目只实现了属性注入功能,所以不存在循环依赖问题,并且降低了项目复杂度,所以暂时不考虑构造函数参数注入,除非将来遇到了属性注入不能解决的问题。
14. 延迟初始化 / 按需加载
- 当前
LazyInject是属性级别的延迟注入,但缺少 binding 级别的延迟加载 - 支持
bind(token).toLazyClass(() => import('./heavy-service').then(m => m.HeavyService)) - 适用于大型应用中按需加载模块的场景
作者态度
暂时不考虑。服务一般不会特别重。
15. 装饰器组合
- 支持将多个装饰器组合成一个,减少样板代码
- 比如
const InjectOptional = compose(Inject, Optional)或者@Inject(token, { optional: true, self: true }) - 当前需要在同一个属性上堆叠多个装饰器,可读性不够好
作者态度
暂时不考虑。
16. 测试辅助工具
- 提供
createTestingContainer()工厂方法,内置 mock/stub 支持 - 支持
container.rebind(token).toConstantValue(mockInstance)方便替换依赖 - 参考 NestJS 的
Test.createTestingModule()设计
作者态度
暂时不考虑。
17. 性能优化方向
- 当前
_getInjectProperties每次解析实例时都会遍历继承链合并元数据,可以考虑缓存合并结果 Binding._resolvers使用字符串映射到方法名再通过(this as any)[resolver]调用,可以直接映射到函数引用,避免字符串查找- 大量绑定时,
Map的性能已经很好,但如果需要极致性能,可以考虑分层索引
作者态度
暂时不考虑。
18. 错误体验优化
- 当前错误信息已经不错,但可以参考 Angular 的错误码系统(如
NG0200: Circular dependency),每个错误附带文档链接 - 在开发模式下提供更详细的诊断信息,生产模式下精简错误消息以减小包体积
- 循环依赖错误可以展示完整的依赖路径图(A → B → C → A)
作者态度
暂时不考虑。本项目不存在循环依赖问题。
19. 与框架集成的标准化
- 当前已有
@kaokei/use-vue-service与 Vue 集成,可以考虑提供更通用的集成接口 - 定义一个
ContainerAdapter接口,让不同框架(React、Vue、Svelte)都能以统一方式接入 - 参考 InversifyJS 的
inversify-react、inversify-express-utils等生态
作者态度
暂时不考虑。目前只提供给 @kaokei/use-vue-service 使用。不考虑react,因为react的基于计算的模型,天然和响应式数据模型不太契合。除非引入编译器,否则很难提供友好的API。
20. 不可变容器(Frozen Container)
- 提供
container.freeze()方法,冻结后不允许再添加/修改绑定 - 适用于应用启动完成后锁定配置,防止运行时意外修改
- 参考 Spring 的
ApplicationContext在 refresh 后不可变的设计
作者态度
暂时不考虑。