Repository navigation
ADR-0116 收尾:12 个消费方在 init() 硬取服务却未声明 requiresServices(均已被硬依赖保护,是诊断质量而非正确性问题) #4187
Description
ADR-0116(#4131,PR #4163)建立了声明式排序契约;PR #4185 补齐了 provider 侧的 20 个 providesServices。本 issue 记录审计的另一半——消费方侧,以及一个反直觉但重要的结论:这一批不是活的 bug。
审计结论
对每个插件大括号配对提取 init() 方法体(剥离注释后),按调用是否位于 try/if 内分类,发现 12 个插件在 init() 里同步硬取服务(其中 11 个取的是 manifest)却没有声明 requiresServices:
| 插件 | init 硬取 | 已声明的 dependencies |
|---|---|---|
com.objectstack.audit |
manifest |
['com.objectstack.engine.objectql'] |
com.objectstack.service.reports |
manifest |
同上 |
com.objectstack.service.approvals |
manifest |
同上 |
com.objectstack.service.sharing |
manifest |
同上 |
com.objectstack.service.email |
manifest |
同上 |
com.objectstack.security |
manifest |
同上 |
com.objectstack.service.realtime |
manifest |
同上 |
com.objectstack.service.messaging |
manifest |
同上 |
com.objectstack.auth |
data, manifest |
dependencies: string[] = [...objectql] |
com.objectstack.metadata.protocol |
objectql |
对象字面量 dependencies: [...objectql] |
com.objectstack.plugin-webhook-outbox |
manifest |
['com.objectstack.service.messaging'] → 传递到 objectql |
customer-plugin(packages/core/examples/api-registry-example.ts) |
api-registry |
示例文件 |
12 个全部已经通过声明的硬依赖被正确排序,没有一个是活的 #4085 类暴露。plugin-webhook-outbox 是唯一靠传递依赖保护的(→ messaging → objectql),能跑,但比其余的脆一层。
排查提示,免得后来者重蹈:我中途一度判定
plugin-auth与metadata-protocol"无任何依赖声明、属于活的暴露"——是错的,grep 模式的问题。前者写作dependencies: string[] = [...](名字与=之间有类型标注),后者写作对象字面量的dependencies: [...](冒号非等号)。搜依赖声明时两种语法都要覆盖。
因此:不建议批量补 requiresServices
对已声明硬依赖的消费方,requiresServices 大体上是在重复内核已经强制的事——缺 provider 时内核先报的是 Dependency 'com.objectstack.engine.objectql' not found,已经够指名道姓了。硬加一遍收益很小,却要在 12 个文件里引入需要逐个判读的语义。
requiresServices 真正不可替代的场合是软依赖消费方:插件在 provider 缺席时要降级(所以不能用硬 dependencies),但在 provider 在场时又必须排在它后面。目前全仓只有 AppPlugin 一个,已在 #4163 声明。
建议的处理
- 新插件写进检查清单:在
init()里同步取服务时,二选一——要么声明硬dependencies(provider 必然在场),要么optionalDependencies+requiresServices(可降级)。裸取且不声明是os serve <config>cannot boot without a prebuiltdist/objectstack.json— dies withService 'manifest' is async - use await#4085 的配方。 plugin-webhook-outbox值得显式化:它对manifest的依赖目前挂在messaging → objectql这条传递链上,messaging 哪天不再依赖 objectql 就会断。加一条dependencies直连 objectql,或加requiresServices: ['manifest']让失败可指名。plugin-auth有一处矛盾代码(顺带发现,可另开):init 里const dataEngine = ctx.getService<any>('data'); if (!dataEngine) { warn('No data engine service found - auth will use in-memory storage') }—— 但getService在服务缺失时是抛错而不是返回undefined,所以那条降级分支在 ObjectKernel/LiteKernel 上是死代码。要么改成 try/catch 真正降级,要么删掉分支并声明依赖,别让代码宣称一种它并不具备的容错。- ADR-0116 D4 的复评时机:等声明覆盖面稳定后,再决定要不要从
requiresServices/providesServices自动推导排序。当前证据反而支持继续不做——provider 侧刚补齐、消费方侧一致选择了硬依赖,说明现有的两级机制(硬依赖排序 + 服务声明校验/诊断)已经覆盖了真实用法。
审计脚本
审计用的一次性脚本没有入库(它是排查工具,不是产品代码)。若要做成常驻 lint,要点是:大括号配对提取 init() 体(正则必须容忍 ): Promise<void> 这种返回类型标注,否则会漏掉本仓库的主流写法——第一版就因此只扫到 10 个文件而非 35 个)、先剥离注释(否则 TSDoc 里的 ctx.getService('manifest') 会被当成真实依赖)、再按 try/if 嵌套判定是硬取还是可降级。
Activity
chore(plugins,services): declare providesServices on the remaining in…
fix(plugin-auth,plugin-webhooks): retire a dead degrade branch and an…