多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

[DeepSeek Harness插件内核-17]将针对服务的硬依赖转换成软依赖

[DeepSeek Harness插件内核-17]将针对服务的硬依赖转换成软依赖 Cordis插件针对服务的消费具有一个基本的原则只有在显式注入的前提下才能消费目标服务。但是一旦完成了服务注入Cordis就会认为当前插件没有依赖服务的参与就不能正常工作所以它会将插件的生命周期和服务生命周期绑定在一起。对于插件来说只有它所有注入的依赖服务全员上线它才能上线反之如果在运行过程中有任何一个依赖服务下线自己也改不了下线的命运所以插件与服务之间就是这种硬性依赖的关系。但是很多时候插件需要保持始终处于激活的状态消费的服务也不是必需的就需要通过一些变通将针对服务的依赖变得软性一点。1. 插件只能消费显式注入的服务先注入后消费是Cordis进行服务调用的一条铁律。以如下的演示程序为例我们定义了FooService和BarService这两个实现了FoobarService接口的服务并在它们的invoke方法中输出一点指示性文字确定当前调用的服务类型。我们调用plugin方法将这两个服务注册到创建的Context中并在另一个通过调用inject方法注册的插件中试着消费这两个服务。import{Context,Service}fromdeepseek-ai/cordisinterfaceFoobarService{invoke():void}declaremoduledeepseek-ai/cordis{interfaceContext{foo:FoobarService bar:FoobarService}}classFooServiceextendsService{constructor(ctx:Context){super(ctx,foo)}invoke(){console.log(FooService.invoke() is invoked!)}}classBarServiceextendsService{constructor(ctx:Context){super(ctx,bar)}invoke(){console.log(BarService.invoke() is invoked!)}}constctxnewContext()ctx.plugin(FooService)ctx.plugin(BarService)ctx.inject([foo],ctx{for(constserviceof[()ctx.foo,()ctx.bar]){try{service().invoke();}catch(err){console.log(err)}}})输出FooService.invoke()is invoked!Error: cannot get propertybarwithout inject atanonymous(E:\dsh\deepseek-harness\explor\src\downgrade.ts:36:49)at Object.ctx(E:\dsh\deepseek-harness\explor\src\downgrade.ts:38:13)at Fiber.execute(E:\dsh\deepseek-harness\vendor\cordis\src\fiber.ts:259:28)atanonymous(E:\dsh\deepseek-harness\vendor\cordis\src\fiber.ts:366:45)at composeError(E:\dsh\deepseek-harness\vendor\cordis\src\utils.ts:272:25)at Fiber._execute(E:\dsh\deepseek-harness\vendor\cordis\src\fiber.ts:358:12)at Fiber._reload(E:\dsh\deepseek-harness\vendor\cordis\src\fiber.ts:656:20)at process.processTicksAndRejections(node:internal/process/task_queues:104:5)由于inject方法只指定了foo服务所以插件中只能调用注册的FooService。调用BarService会直接抛出异常并提示cannot get property bar without inject。2. 插件生命周期受控于依赖的服务Cordis内部会维护一个服务与插件依赖于它的插件所在Fiber的映射关系。当某个服务下线时Cordis会利用这个映射找到对用的Fiber对象并使用它让插件也下线。反之当服务重写上线后也会便利所有的映射的Fiber如果对应插件注入的服务全部处于激活的状态对应的插件会被重新拉起来。下面就是一个典型的例子import{Context,Service}fromdeepseek-ai/cordis...functiondelay(ms:number):Promisevoid{returnnewPromise(resolvesetTimeout(resolve,ms))}constctxnewContext()constfooctx.plugin(FooService)constbarctx.plugin(BarService)ctx.inject([foo,bar],ctx{console.log(plugin starts...)consttimersetInterval(()console.log(Plugin is alive.),1000)ctx.effect(()(){console.log(Plugin is disposed...)clearInterval(timer)})})ctx.plugin(asyncctx{awaitdelay(3000)foo.restart();awaitdelay(3000)bar.dispose();delay(10000)})如代码所示我们调用inject方法同时注入了foo和bar服务注册了一个插件该插件会在启动的时候输出plugin starts...,然后以1秒为间隔输出Plugin is alive.表明自己还活着。我们通过调用Context的effect方法输出Plugin is disposed...以为着这段文件会在插件下线的时候被输出。在另一个注册的插件中会在启动后3秒时调用注册FooService返回的Fiber的restart方法实施重启然后在6秒后调用FooService对应Fiber的dispose方法。整个程序运行后会生成如下的输出plugin starts... Plugin is alive. Plugin is alive. Plugin is disposed... plugin starts... Plugin is alive. Plugin is alive. Plugin is disposed...从输出结果可以看出注册插件的生命周期完全收到依赖服务的控制。任一依赖服务所在Fiber的重启都将导致插件的重启前提时所有依赖服务全员在线如果任何一个依赖服务下线后没有再起来插件将永远来起不来。3. 既能消费服务又保持插件独立的生命周期如果消费的服务并非插件必需比如我们在它上面应用了降级策略当检测到某个服务不可能时立即使用另一个服务来替换这是保持可用性的常用策略。但是如何将这套策略应用到某个插件上呢现在面临的困境在于消费服务必需注入注入就导致两者之间生命周期的依赖。其实问题很好解那就是将服务注入到之间的子插件中并利用后者来提供消费的服务和检测服务的状态。以如下的演示程序为例我们直接调用plugin方法注册了我们的插件并定义了两个FoobarService|undefined类型的primary和secondary变量分别表示主服务和后背的降级服务。插件会以1秒的间隔持续调用服务如果primary不可用就使用secondary。import{Context,Service}fromdeepseek-ai/cordis...constctxnewContext()constfooctx.plugin(FooService)ctx.plugin(BarService)ctx.plugin(ctx{letprimary:FoobarService|undefinedletsecondary:FoobarService|undefinedctx.inject([foo],ctx{primaryctx.fooreturn()primaryundefined})ctx.inject([bar],ctx{secondaryctx.barreturn()secondaryundefined})consttimersetInterval(()(primary??secondary)?.invoke(),1000)ctx.effect(()()clearInterval(timer))})awaitdelay(3000)console.log(unload FooService...)foo.dispose()awaitdelay(3000)console.log(register FooService...)ctx.plugin(FooService)插件内部两次调用inject方法基于注入的foo和bar服务注入了两个子插件。如果它们上线意味着依赖的服务必然处于激活状态此时它们分别对primary和secondary变量进行设置。由于这两个子插件没有复杂的操作foo或者bar服务就是它们唯一的依赖初始它们下线的唯一原因就是服务在可用此时只需要将primary或者secondary设置为undefined即可。在上面的演示程序中我们会在插件注册3秒后调用FooService所在Fiber的disopose方法意味着此时主服务不再可用后备的降级服务BarService会自动顶上去。程序运行的输出结果体现了这一点。再等3秒我们重新注册FoobarService插件将再次使用此服务。FooService.invoke()is invoked!FooService.invoke()is invoked!unload FooService... BarService.invoke()is invoked!BarService.invoke()is invoked!BarService.invoke()is invoked!register FooService... FooService.invoke()is invoked!FooService.invoke()is invoked!FooService.invoke()is invoked!...
返回列表