Skip to content

ADR-0116 收尾:12 个消费方在 init() 硬取服务却未声明 requiresServices(均已被硬依赖保护,是诊断质量而非正确性问题) #4187

Description

@os-zhuang

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-pluginpackages/core/examples/api-registry-example.ts api-registry 示例文件

12 个全部已经通过声明的硬依赖被正确排序,没有一个是活的 #4085 类暴露。plugin-webhook-outbox 是唯一靠传递依赖保护的(→ messaging → objectql),能跑,但比其余的脆一层。

排查提示,免得后来者重蹈:我中途一度判定 plugin-authmetadata-protocol "无任何依赖声明、属于活的暴露"——是错的,grep 模式的问题。前者写作 dependencies: string[] = [...](名字与 = 之间有类型标注),后者写作对象字面量的 dependencies: [...](冒号非等号)。搜依赖声明时两种语法都要覆盖。

因此:不建议批量补 requiresServices

已声明硬依赖的消费方,requiresServices 大体上是在重复内核已经强制的事——缺 provider 时内核先报的是 Dependency 'com.objectstack.engine.objectql' not found,已经够指名道姓了。硬加一遍收益很小,却要在 12 个文件里引入需要逐个判读的语义。

requiresServices 真正不可替代的场合是软依赖消费方:插件在 provider 缺席时要降级(所以不能用硬 dependencies),但在 provider 在场时又必须排在它后面。目前全仓只有 AppPlugin 一个,已在 #4163 声明。

建议的处理

  1. 新插件写进检查清单:在 init() 里同步取服务时,二选一——要么声明硬 dependencies(provider 必然在场),要么 optionalDependencies + requiresServices(可降级)。裸取且不声明是 os serve <config> cannot boot without a prebuilt dist/objectstack.json — dies with Service 'manifest' is async - use await #4085 的配方。
  2. plugin-webhook-outbox 值得显式化:它对 manifest 的依赖目前挂在 messaging → objectql 这条传递链上,messaging 哪天不再依赖 objectql 就会断。加一条 dependencies 直连 objectql,或加 requiresServices: ['manifest'] 让失败可指名。
  3. 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 真正降级,要么删掉分支并声明依赖,别让代码宣称一种它并不具备的容错。
  4. ADR-0116 D4 的复评时机:等声明覆盖面稳定后,再决定要不要从 requiresServices/providesServices 自动推导排序。当前证据反而支持继续不做——provider 侧刚补齐、消费方侧一致选择了硬依赖,说明现有的两级机制(硬依赖排序 + 服务声明校验/诊断)已经覆盖了真实用法。

审计脚本

审计用的一次性脚本没有入库(它是排查工具,不是产品代码)。若要做成常驻 lint,要点是:大括号配对提取 init() 体(正则必须容忍 ): Promise<void> 这种返回类型标注,否则会漏掉本仓库的主流写法——第一版就因此只扫到 10 个文件而非 35 个)、先剥离注释(否则 TSDoc 里的 ctx.getService('manifest') 会被当成真实依赖)、再按 try/if 嵌套判定是硬取还是可降级。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions