← 返回目录

第 2 卷

第 2 / 5 卷日期:

响应式依赖

我们再来看看传统 IoC(控制反转)容器是怎么做依赖注入的。

我们熟悉的 Spring、NestJS、Angular 等框架,处理依赖的方式基本一致:在启动时,按依赖图一次性把对象都创建好、注入进去。如果某个 bean 依赖另一个 bean,容器就保证先创

建被依赖的那个。本质上,这是“组件启动时拉取依赖”——组件被创建的那一刻,向容器要它需要的服务。

它们也并非完全不能动态调整,不少框架提供了运行时重配置的 API。但关键在于:这些动态能力通常是附加的、手动的。当你改变一个依赖时,依赖方的状态一致性、资源清理、启动 /

停止顺序,仍然需要你自己编写代码来维护。框架不会主动告诉你“某个服务没了,依赖它的组件现在该怎么办”。

这在传统服务里通常可以接受,因为服务一旦启动,组件就基本稳定了,动态调整的需求并不频繁。然而 Cordis 的设计场景和目标恰恰相反:它要支持的是插件在运行中被热替换、被动

态装卸。依赖图不再是一张静态的图,而是一个随时可能变化的动态图。如果继续沿用“手动维护依赖变化”的思路,复杂度会迅速失控。

论文把这个差异讲得很透彻。它把传统 IoC 的“键值对注入”升级成了一个叫 reactive coeffects(响应式协效应)的模型。核心思想就一句话:把依赖关系从“启动时的一次性解

析”,变成“每次上下文变化都要重新评估”。

论文里用了一个很精炼的定义来刻画这件事。组件声明一个“依赖规格”(specification)—— 也就是“我需要哪些服务”。每当共享上下文发生变化(比如某个服务被提供、被撤销),框架

就会对照着这个规格把这次变化归类:

activating(激活):依赖从“不满足”变成了“满足” → 组件该启动了

deactivating(停用):依赖从“满足”变成了“不满足” → 组件该被卸载了

图 5-1
图 5-1

正是“对照规格给每一次变化分类”这个动作,让“依赖满足与否的变化”总能被当场发现,而不是等到某个插件崩溃了才发现。这就是“响应式”的含义。

响应式依赖是怎么实现的?

前面我们用 greeter/consumer 看清了 Service/inject 的静态用法。下面我们让 greeter 中途消失、再回来,亲眼看看「服务消失 → 插件自动停,服务恢复 → 插件自动起」的动态

用法:

复制代码 1import { Context } from '@deepseek-ai/cordis'

2

3//一个"服务":提供greet能力。为了看清"换服务",让每次提供的返回内容不同
4functionprovideGreeter(ctx: Context, tag: string) {
5ctx.provide('greeter', {
6greet:(who: string) =>`[${tag}] Hello,${who}!`,
7})
8}
10//一个"依赖者":声明我需要greeter,框架保证greeter就绪后我才被调用
11constconsumer = {

17 18async function main() {

19const root = new Context()

20

21// 1.先提供greeter,再挂载consumer —— consumer直接启动
22constgreeterFiber =awaitroot.plugin((ctx) =>provideGreeter(ctx,'v1'))
23console.log('[greeter:v1]已上线')
24awaitroot.plugin(consumer)

25

26// 2.撤销greeter —— consumer检测到依赖消失,自动停止
27console.log('---撤销greeter ---')
28awaitgreeterFiber.dispose()
29console.log('[greeter:v1]已下线')

30

31// 3.换一个新的greeter上线—— consumer自动重启
32console.log('---新greeter上线---')
33awaitroot.plugin((ctx) =>provideGreeter(ctx,'v2'))
34}

35 36main()

运行 npx tsx src/hello-inject.ts,你会看到:

复制代码 1[greeter:v1] 已上线

2[consumer] [v1] Hello, world!

3--- 撤销 greeter --- 4[greeter:v1] 已下线

5--- 新 greeter 上线 --- 6[consumer] [v2] Hello, world!

注意:第一次 consumer 收到的是 [v1],第二次收到的是 [v2]。你没有写任何启动或停止 consumer 的调度代码,它只是因为 inject: ['greeter'] 这一行声明,就在

greeter 消失时自动闭嘴、在 greeter 回来时自动开口。

全站分卷索引 · 5 卷
  1. 1第 1 卷
  2. 2第 2 卷
  3. 3第 3 卷
  4. 4第 4 卷
  5. 5第 5 卷