Cordis 入门 04:用 service 和 inject 管理插件依赖
事件适合广播消息,但有些场景需要明确依赖一个能力。例如日志插件提供 logger,数据库插件提供 database,业务插件必须拿到这些服务后才能运行。
Cordis 用 ctx.provide() 提供服务,用 inject 声明依赖。这个组合是 Cordis 插件协作的核心。
这篇要练什么
本文会实现一个 counter 服务和一个消费它的插件。
你会掌握:
- 用
ctx.provide()提供服务。 - 用
inject声明插件依赖。 - 理解“先安装消费者,后安装提供者”为什么也能工作。
- 理解服务消失后,依赖插件为什么需要停用。
提供一个 counter 服务
把 index.mjs 改成下面这样:
import { Context } from 'cordis'
const ctx = new Context()
function counterProvider(ctx) {
const counter = {
value: 0,
inc() {
return ++this.value
},
}
ctx.provide('counter', counter)
}
await ctx.plugin(counterProvider)
console.log(ctx.counter.inc())
console.log(ctx.counter.inc())运行:
node index.mjs输出:
1
2ctx.provide('counter', counter) 把 counter 注册成服务。服务注册后,可以通过 ctx.counter 读取。
这个例子在 root 上下文直接读取服务,是为了演示效果。真实插件里更推荐通过 inject 明确声明依赖。
写一个消费服务的插件
现在把消费逻辑写成插件。
import { Context } from 'cordis'
const ctx = new Context()
function counterProvider(ctx) {
const counter = {
value: 0,
inc() {
return ++this.value
},
}
ctx.provide('counter', counter)
}
const counterConsumer = Object.assign((ctx) => {
console.log('counter:', ctx.counter.inc())
}, {
inject: ['counter'],
})
await ctx.plugin(counterProvider)
await ctx.plugin(counterConsumer)输出:
counter: 1inject: ['counter'] 表示 counterConsumer 依赖 counter 服务。Cordis 会在运行插件前检查依赖是否满足。
先安装消费者会怎样
把安装顺序反过来。
import { Context } from 'cordis'
const ctx = new Context()
function counterProvider(ctx) {
const counter = {
value: 0,
inc() {
return ++this.value
},
}
ctx.provide('counter', counter)
}
const counterConsumer = Object.assign((ctx) => {
console.log('counter:', ctx.counter.inc())
}, {
inject: ['counter'],
})
const consumerFiber = ctx.plugin(counterConsumer)
await ctx.plugin(counterProvider)
await consumerFiber输出仍然是:
counter: 1这就是 Cordis 和普通手写依赖调用的差别。counterConsumer 先被安装,但它依赖的 counter 不存在,所以 Cordis 暂时不激活它。等 counterProvider 提供服务后,Cordis 会通知依赖 counter 的插件,消费者才开始执行。
这个模型很适合插件系统,因为插件安装顺序不一定总是固定的。
服务也应该随插件卸载
ctx.provide() 本身也是一种副作用。提供服务的插件卸载后,它提供的服务也应该消失。
试试这个例子:
import { Context } from 'cordis'
const ctx = new Context()
function counterProvider(ctx) {
ctx.provide('counter', {
value: 0,
inc() {
return ++this.value
},
})
}
const counterConsumer = Object.assign((ctx) => {
ctx.effect(() => {
console.log('consumer active:', ctx.counter.inc())
return () => console.log('consumer inactive')
}, 'consumer lifecycle')
}, {
inject: ['counter'],
})
const consumerFiber = ctx.plugin(counterConsumer)
const providerFiber = ctx.plugin(counterProvider)
await consumerFiber
await providerFiber.dispose()输出类似:
consumer active: 1
consumer inactive当 counterProvider 卸载后,counter 服务消失。依赖 counter 的 counterConsumer 也会进入卸载流程。
小练习:做一个 logger 服务
需求:
loggerProvider提供appLogger服务。messagePlugin依赖appLogger。messagePlugin监听demo/message事件,并用appLogger.info()打印消息。
参考实现:
import { Context } from 'cordis'
const ctx = new Context()
function loggerProvider(ctx) {
ctx.provide('appLogger', {
info(message) {
console.log(`[info] ${message}`)
},
})
}
const messagePlugin = Object.assign((ctx) => {
ctx.on('demo/message', (text) => {
ctx.appLogger.info(`message received: ${text}`)
})
}, {
inject: ['appLogger'],
})
const messageFiber = ctx.plugin(messagePlugin)
await ctx.plugin(loggerProvider)
await messageFiber
ctx.emit('demo/message', 'hello service')输出:
[info] message received: hello service这个练习把第 3 篇的事件和本文的服务组合起来了。事件负责传递消息,服务负责提供可复用能力。
入门检查点
服务依赖要记住两句话:
提供能力用 ctx.provide()
消费能力用 inject 声明依赖有了 provide + inject,Cordis 才能知道一个插件什么时候可以激活,也能在服务消失时停用依赖插件。下一篇我们继续看插件组合:如何用一个父插件安装多个子插件,把功能打包成一个更大的能力。
系列导航:
