跳到主要内容

DI 框架差距分析

对比 NestJS、InversifyJS、tsyringe、Angular DI、awilix 等主流框架的分析结果。

当前已实现的核心功能

  • ClassProvider(singleton)/ ValueProvider(支持 multi: true)/ FactoryProvider(transient)
  • 构造函数注入 + @Inject + @Optional 装饰器
  • 父子层级 Injector(@TpRoot 实现作用域隔离)
  • @TpModule 模块系统(providers + imports)
  • @OnStart / @OnTerminate 生命周期钩子
  • 运行时循环依赖检测
  • Decorator 继承体系(make_decorator / make_abstract_decorator,便于框架扩展)
  • Provider 覆盖(Injector.set() 使用 Map.set() 语义,后注册覆盖前者)
  • 启动时依赖图校验(TpAssembly / TpEntry 在加载时立即 create(),触发完整依赖链解析)
  • 类型安全注入(Injector.get<T>(token: Constructor<T>) 重载签名,class token 自动推断类型)
  • 诊断工具(inspect_injector() 树形输出、check_usage 未使用 provider 检测、detect_cycle_ref 循环依赖检测)

与主流框架的差距评估

高优先级(均不需要做)

功能决策理由
Transient 作用域不做ClassProvider 天然 singleton,FactoryProvider 天然 transient,两种 provider 类型已覆盖两种需求。需要缓存的工厂场景写包装 class 用 ClassProvider 注册即可。
Request 作用域不做HTTP 模块已在请求闭包中手动创建 TpRequest/HttpContext 等请求级对象,不经过 Injector,天然实现请求级生命周期。如需在深层 service 访问请求上下文,可用 AsyncLocalStorage 传播。
异步 FactoryProvider不做@OnStart 钩子已覆盖异步初始化场景,在 Platform.start() 阶段统一执行。改 Provider.create() 为异步返回会导致整个依赖解析链路异步化,属于破坏性变更,代价远大于收益。
动态模块暂不做NestJS 的 forRoot(config) 本质是装饰器执行时传入硬编码常量,运行时配置仍需 forRootAsync + FactoryProvider。tarpit 的 TpConfigData + FactoryProvider 已能覆盖运行时配置需求。除非出现当前实现非常麻烦的具体场景,再考虑引入。
属性注入不做打破循环依赖应通过重构解决,减少构造函数参数是职责过重的代码气味。Angular 的 inject() 函数方案引入全局上下文依赖,混用增加心智负担,全面切换导致所有 class 无法脱离容器测试。构造函数注入是更干净的设计约束。

中优先级(均不需要做)

功能决策理由
ForwardRef不做本质是循环依赖问题,即使解决加载顺序,运行时实例化仍是死循环,需配合属性注入才能断开。既然属性注入不做,应通过重构消除循环依赖。
Provider 覆盖已支持Injector.set() 使用 Map.set() 语义,后注册的同 token provider 直接覆盖前者。测试时 platform.import({ provide: Token, useValue: mock }) 即可替换。
显式模块导出不做tarpit 不面向超大型应用,所有 provider 对上层可见的设计足够简洁实用,无需引入 exports: [] 增加配置复杂度。
惰性注入不做ClassProvider 是 singleton 只创建一次,依赖树深度有限,启动性能不构成问题。条件分支依赖可通过 FactoryProvider 或拆分 service 解决。

低优先级(基本已实现)

功能现状差距
启动时依赖图校验TpAssembly/TpEntry 在加载时立即 create(),触发完整依赖链解析FactoryProvider 的 deps 引用不存在的 token 时不会提前报错(极少数场景)
类型安全 TokenInjector.get<T>() 对 class token 已有类型推断string/symbol token 缺少 InjectionToken<T> 封装(极少使用)
诊断工具inspect_injector() 树形输出、check_usage 函数、detect_cycle_ref 均已实现check_usage 未集成到启动流程,可视化格式可优化

结论

tarpit 的 DI 核心实现已覆盖主流框架的绝大多数功能,部分看似缺失的功能实际上通过不同的设计方式已经解决(如 FactoryProvider 天然 transient、HTTP 闭包天然 request scope、@OnStart 覆盖异步初始化)。与主流框架的差异更多是设计取舍而非功能缺失,当前设计在简洁性和实用性之间取得了合理的平衡。