第 5 章 动手实战(二):服务与依赖注入
服务(Service) 是一个插件提供、其他插件通过 ctx 消费的具名能力。在 harness 中,ctx.tools、ctx.llm、ctx.agents 都是服务。消费方只指定 'tools' 这样的能力名,而不导入其提供方——因此配置可以选择提供方,无需修改消费方。
本章对应官方教程第 3 章。
5.1 提供服务
创建 greeter.ts:
两部分协同工作:
运行时:
super(ctx, 'greeter')以名称greeter注册实例,任何插件此后可通过ctx.greeter访问。注册属于 effect,卸载提供方时会移除该服务。编译时:
declare module '@deepseek-ai/cordis'块用 TypeScript 声明合并把greeter加入Context接口,使ctx.greeter通过类型检查。它不生成任何代码——没有该声明时服务运行时仍工作,只是失去类型安全。
Service 子类本身就是插件(第 4 章的类形态),所以 ctx.plugin(GreeterService) 像挂载其他插件一样挂载它。
5.2 用 inject 消费服务
创建 consumer.ts:
组合并运行(cordis.yml):
输出 Hello, world!。交换两行顺序后输出不变——决定插件何时启动的是依赖关系,而不是文件顺序。
5.3 依赖跟踪不会在加载后停止
inject 并非一次性的启动检查:
所需服务在应用运行期间消失(提供方被卸载或热替换)→ 每个依赖插件也随之卸载;
服务恢复后 → 依赖插件再次加载。
结合 effect,这能防止运行中的消费方保留对不可用服务的引用。这也是配置可以替换服务的原因:卸载 dsh-bash-local、挂载另一个 shell 提供方,所有注入 'shell' 的插件都会重启并使用新实现。
5.4 依赖缺失时的行为
尝试彻底移除 ./greeter.ts 再运行:
消费方保持 PENDING,不输出任何内容,既不崩溃,也不会"运行一半";
PENDING 的 fiber 不会让 Node 事件循环保持活跃——如果组合里没有其他运行项,进程会静默以状态码 0 退出。
这是初学者最容易踩的坑,第 7 章会专门讲诊断方法。
5.5 可选依赖
inject 用于硬性依赖。如果某项功能缺失时插件仍应运行,请跳过 inject,在使用处探测:
5.6 服务命名
一个应用中的服务名共用一个扁平命名空间。给自有服务添加有辨识度的前缀或命名空间(harness 已占用 tools、llm 等普通名称)。子系统页面上生成的 cordis-surface 区块列出 harness 注册的每一个名称。
依据官方教程第 3 章(03-services)整理。