Skip to content

Demo 3 · 依赖注入与组合

目标:跑通 inject 的"等待与级联",理解 isolate 作用域,并见识一个真实的时序陷阱。对应原理篇 02.3–02.4。代码:demos/03-compose/main.ts

运行

sh
cd demos
npm run demo:3

预期输出(节选):

text
== 1. 先挂 reporter(依赖未满足,保持 PENDING)==
   reporter fiber 状态:PENDING
== 2. 依次挂 database 与 cache ==
   不 await 立即读 root.database = undefined
[database] 连接已建立:postgres://localhost/demo
   await 之后读 root.database  = [object Object]
[cache] 缓存服务已就绪(database 一定先就绪)
[reporter] 启动,看到的 database = postgres://localhost/demo
   reporter fiber 状态:ACTIVE
== 4. isolate:子作用域里换一套 database ==
[database] 连接已建立:sqlite://:memory:
[reporter] 启动,看到的 database = sqlite://:memory:
== 5. 卸载 database:观察级联 ==
   根 reporter fiber 状态:PENDING(回到 PENDING 等新 database)

输出解读

① PENDING:等待不是错误

reporter 声明 inject: ['database', 'cache'],被挂载时两个服务都不存在。它的 fiber 停在 PENDING,不报错、不执行、静默等待。服务齐了才变成 ACTIVE。这就是"加载顺序由依赖推导"的运行形态。

② 时序陷阱:直接读属性的不可靠

text
不 await 立即读 root.database = undefined
await 之后读 root.database  = [object Object]

同一个 root.database,在提供者 fiber 加载完成前后读到的东西不一样。这正是 dsh 要求跨插件访问一律走 inject 的原因:inject 有类型保证(编译期)、有顺序保证(就绪才执行)、有级联保证(卸载一起走)。

③ 级联卸载

卸载 database 后,reporter 回到 PENDING、cache 被级联卸载,而子作用域(sqlite)不受影响。依赖关系是双向可追溯的:提供者没了,消费者全部退回等待;新的提供者出现,消费者自动复活。这就是 dsh 热重载的基础。

④ isolate:同名服务,两个世界

ts
const child = root.isolate('database')      // database 服务走独立实现
child.plugin(DatabaseService, 'sqlite://:memory:')

同一个 reporter 插件(同样的 inject 声明),挂在根作用域看到 postgres,挂在子作用域看到 sqlite。插件代码零改动,看到的世界由挂载位置决定——dsh 的 agent preset 就是这个机制:全局工具在根,agent 专属工具在 agent.ctx

亲手做实验

实验 1:观察 cache 的启动日志

输出里 cache 只在 database 之后启动过。试着只挂 cache 不挂 database,cache 会怎样?

实验 2:两个 isolate 共享作用域

isolate('database') 传第二个参数(同一个 label symbol)给两个子上下文,它们会共享同一套 database 实现。验证一下。

实验 3:intercept

代码末尾的 intercept 只是演示了 API。试着给 database 服务加一个 config 类型(Service 的第二个类型参数),体会 intercept 如何为下方插件合并配置。

常见错误

现象原因
cannot get required service "xxx" in inactive context在插件里读了 inject 声明之外的服务属性
插件永远不启动inject 的服务名拼错 / 提供者根本没挂
改了配置没生效把"读属性"当成了依赖声明——记住正路是 inject

下一步

前三个 Demo 都在 Cordis 的"真空"里。从 Demo 4 开始,我们进入 dsh 本体:把 Mock 适配器注册进真实的 LLM 服务。

基于 DeepSeek Harness(开发者预览版 0.1.0-rc.6)与 Cordis 撰写