☰
[DeepSeek Harness深度拆解-19]通过继承SettingsProvider构建自己的配置系统
2026/10/1 8:21:53 网站建设 项目流程

DeepSeek Harness深度拆解-18:DSH配置服务设计详解围绕着SettingsProvider对DSH的配置系统的设计进行了系统的梳理,这是我们对配置系统进行定制的基础。在这篇文章中,我么将通过继承这个抽象的SettingsProvider,提供一个简单的将内存字典作为数据源的配置系统。

1. 基于内存的配置系统

如下这个MemorySettingsProvider派生于抽象类SettingsProvider,使用构造函数提供的Record<string, unknown>对象作为配置源,同时绑定到config字段上。本着怎么简单怎么来的原则,我们只定义了不得不重写的三个抽象成员。其中writable返回true以支持配置的写入,load方法将config字段作为加载的配置,persist方法则将指定的持久化配置写入config字段。

import{Context}from'@deepseek-ai/cordis'import{SettingsProvider,SettingsNamespace}from'@deepseek-ai/dsh-settings'importzfrom'@deepseek-ai/schemastery'classMemorySettingsProviderextendsSettingsProvider{readonlywritable:booleanconstructor(ctx:Context,privateconfig:Record<string,unknown>){super(ctx)this.writable=true}protectedload():Promise<Record<string,unknown>>{returnPromise.resolve(this.config)}protectedpersist(ns:SettingsNamespace,section:Record<string,unknown>):Promise<void>{this.config[ns]=sectionreturnPromise.resolve()}}

2. 配置的注册和读取

MemorySettingsProvider虽然简单,但是该有的功能基本都有,接下来我们借助它来进一步演示DSH配置系统的基本编程模式。为了将原始的配置数据解析成对应的强类型的配置对象,我们需要为每个通过命名空间标识的配置节提供一个匹配的schemastery schema,这也是register方法的核心目标。简单起见,我们定义了如下所示的Config接口来表示配置的结构,ConfigSchema则是基于该接口的Schema对象。值得一提的是创建的Schema为四个字段都定义了默认值。

import{Context}from'@deepseek-ai/cordis'import{SettingsProvider,SettingsNamespace}from'@deepseek-ai/dsh-settings'importzfrom'@deepseek-ai/schemastery'...interfaceConfig{foobar:{foo:string,bar:string}baz:stringqux:string}constFoobarScheama:z<{foo:string,bar:string}>=z.object({foo:z.string().default("schema_default_foo"),bar:z.string().default("schema_default_bar"),})constConfigSchema:z<Config>=z.object({foobar:FoobarScheama,baz:z.string().default("schema_default_baz"),qux:z.string().default("schema_default_qux")})constctx=newContext()constsource={test:{qux:"user_qux"}}ctx.plugin(MemorySettingsProvider,source)ctx.inject(["settings"],ctx=>ctx.settings.register("test",ConfigSchema,{base:{baz:"base_baz",qux:"base_qux"},applies:'live'}))ctx.inject(["settings"],ctx=>{console.log(JSON.stringify(ctx.settings.get("test"),null,2))})

我们调用plugin方法将MemorySettingsProvider注册到创建的Context上,并将常量source作为其配置源,对应的值为{test:{qux:"user_qux"}}。在通过inject方法注册的插件中,我们调用ctx.settings.register方法完成了基于ConfigSchema的配置注册,指定的命名空间为test。我们同时利用options参数的base和applies字段指定了配置基准({baz:"base_baz", qux:"base_qux"})和生效的方式(live)。

在另一个采用相同方式注册的插件中,我们调用ctx.settings.get方法将test命名空间对应的配置对象序列化成JSON并输出。从如下的输出接口可以印证上一篇文章介绍的基于schema默认值 => composition base => 用户层的三层组合机制。

输出:

{"foobar":{"foo":"schema_default_foo","bar":"schema_default_bar"},"baz":"base_baz","qux":"user_qux"}

3. 配置的三种更新方式

SettingsProvider通过定义的update、mutate和replace三个方法提供了针对配置的三种更新模式,即增量合并、基于路径的set和unset以及全量替换。在如下的演示程序中,我们在调用inject方法注入settings服务注册的插件中依次调用了上述三个方法,并在每个更新之后调用ctx.settings.get方法提取配置对象,将以JSON的形式进行输出。

import{Context}from'@deepseek-ai/cordis'import{SettingsProvider,SettingsNamespace}from'@deepseek-ai/dsh-settings'importzfrom'@deepseek-ai/schemastery'...constctx=newContext()constsource={test:{qux:"user_qux"}}ctx.plugin(MemorySettingsProvider,source)ctx.inject(["settings"],asyncctx=>{ctx.settings.register("test",ConfigSchema,{base:{baz:"base_baz",qux:"base_qux"},applies:'live'})awaitctx.settings.update("test",{foobar:{foo:"111"},bar:"111"})console.log(JSON.stringify(ctx.settings.get("test"),null,2))awaitctx.settings.mutate("test",[{op:"set",path:["foobar","bar"],value:"222"}])console.log(JSON.stringify(ctx.settings.get("test"),null,2))awaitctx.settings.replace("test",{foobar:{bar:"333"},qux:"333"})console.log(JSON.stringify(ctx.settings.get("test"),null,2))})

输出的三段JSON格式的配置:

{"foobar":{"foo":"111","bar":"schema_default_bar"},"baz":"base_baz","qux":"user_qux","bar":"111"}
{"foobar":{"foo":"111","bar":"222"},"baz":"base_baz","qux":"user_qux","bar":"111"}
{"foobar":{"foo":"schema_default_foo","bar":"333"},"baz":"base_baz","qux":"333"}

由于配置的更新流程最终会涉及到用于持久化配置的persist方法的调用,所以按照如下的方式在更新后输出配置源对象,我们也可以看到类似的变化。由于持久化的内容并非当前配置的全量快照,而是根据当前加载内容进行更新的结果。具体来说,如果采用replace模式,持久化的就是指定的那个对象。对于其他两种模式,被用来持久化的是基于当前加载内容进行合并和修改(set和unset)后的结果。这一点充分体现在输出的JSON上。

import{Context}from'@deepseek-ai/cordis'import{SettingsProvider,SettingsNamespace}from'@deepseek-ai/dsh-settings'importzfrom'@deepseek-ai/schemastery'...constctx=newContext()constsource={test:{qux:"user_qux"}}ctx.plugin(MemorySettingsProvider,source)ctx.inject(["settings"],asyncctx=>{ctx.settings.register("test",ConfigSchema,{base:{baz:"base_baz",qux:"base_qux"},applies:'live'})awaitctx.settings.update("test",{foobar:{foo:"111"},bar:"111"})console.log(JSON.stringify(source,null,2))awaitctx.settings.mutate("test",[{op:"set",path:["foobar","bar"],value:"222"}])console.log(JSON.stringify(source,null,2))awaitctx.settings.replace("test",{foobar:{bar:"333"},qux:"333"})console.log(JSON.stringify(source,null,2))})

输出的三段JSON格式的配置:

{"test":{"qux":"user_qux","foobar":{"foo":"111"},"bar":"111"}}
{"test":{"qux":"user_qux","foobar":{"foo":"111","bar":"222"},"bar":"111"}}
{"test":{"foobar":{"bar":"333"},"qux":"333"}}

4. 监控配置的更新

如果在注册配置的是由将options的applies字段设置成live(默认值),针对配置的更新可以得到实时的应用。register方法返回的SettingsScope<T>提供了watch方法帮助我们实时监控配置的更新,至于更新的来源,可以是来自与绑定的配置源,也可以源自对上述三个方法的调用。

4.1 内部更新

如下的程序演示针对内部更新(在注册的插件中调用ctx.settings.update方法更新配置)的监控。具体来说,在调用register方法完成配置的注册并得到返回的SettingsScope<T>对象后,我们调用其watch方法注册了一个在配置更新时会自动调用的回调,并输出更新前后的配置。

import{Context}from'@deepseek-ai/cordis'import{SettingsProvider,SettingsNamespace}from'@deepseek-ai/dsh-settings'importzfrom'@deepseek-ai/schemastery'...constctx=newContext()constsource={test:{qux:"user_qux"}}ctx.plugin(MemorySettingsProvider,source)ctx.inject(["settings"],asyncctx=>{constscopedSettings=ctx.settings.register("test",ConfigSchema,{base:{baz:"base_baz",qux:"base_qux"},applies:'live'})scopedSettings.watch((next,prev)=>{console.log(`detect settings' change: prev:${JSON.stringify(prev,null,2)}next:${JSON.stringify(next,null,2)}`)})})ctx.inject(["settings"],asyncctx=>ctx.settings.update("test",{foobar:{bar:"222"}}))

输出:

detect settings' change: prev: { "foobar": { "foo": "schema_default_foo", "bar": "schema_default_bar" }, "baz": "base_baz", "qux": "user_qux" } next: { "foobar": { "foo": "schema_default_foo", "bar": "222" }, "baz": "base_baz", "qux": "user_qux" }

4.2 外部更新

对于我们自定义的MemorySettingsProvider来说,它的配置源来源于一个Record<string, unknown>对象。当我们从外部更新了此对象时,需要手工发送一个更新通知,所以我们为MemorySettingsProvider额外定义了一个notifyChange方法,该方法直接调用publish方法重新发布更新后的配置。对于@deepseek-ai/dsh-settings-file默认提供的基于文件的配置系统,这项功能可以通过检测文件的变更来实现。

classMemorySettingsProviderextendsSettingsProvider{publicnotifyChange(){this.publish(this.config)}}

我们将上面演示程序中配置的更新方式改成基于配置源的外部更新。具体来说,我们在最后注册的插件中直接修改了作为配置源的source,然后在调用上面定义的notifyChange方法。

import{Context}from'@deepseek-ai/cordis'import{SettingsProvider,SettingsNamespace}from'@deepseek-ai/dsh-settings'importzfrom'@deepseek-ai/schemastery'...constctx=newContext()constsource={test:{qux:"user_qux"}}ctx.plugin(MemorySettingsProvider,source)ctx.inject(["settings"],asyncctx=>{constscopedSettings=ctx.settings.register("test",ConfigSchema,{base:{baz:"base_baz",qux:"base_qux"},applies:'live'})scopedSettings.watch((next,prev)=>{console.log(`detect settings' change: prev:${JSON.stringify(prev,null,2)}next:${JSON.stringify(next,null,2)}`)})})ctx.inject(["settings"],ctx=>{(source.testasany)["baz"]="222";(ctx.settingsasMemorySettingsProvider).notifyChange()})

输出:

detect settings' change: prev: { "foobar": { "foo": "schema_default_foo", "bar": "schema_default_bar" }, "baz": "base_baz", "qux": "user_qux" } next: { "foobar": { "foo": "schema_default_foo", "bar": "222" }, "baz": "base_baz", "qux": "user_qux" }

4.3 注册settings/updated事件

对于每次配置的更新,除了执行监视器的钩子函数外,还会触发一个名为settings/updated的事件,我们也可以通过此事件得到配置变更的通知。作为事件的处理函数,我们不仅可以利用事件参数得到命名空间和更新前后的配置对象外,还可以得到用以区分内部和外部更新的SettingsUpdateSource(provider或者update)。和其他基于Scope的事件通知机制(事件只能向上传播)不同,该事件时面向全局的,没有Scope的限制。

在如下的演示程序中,我们在一个独立注册的插件中注册了settings/updated事件,并利用指定的回调函数输出了配置的命名空间、更新来源和更新前后的值。在另一个注册的插件中,我们分别实施了内部和外部更新。

import{Context}from'@deepseek-ai/cordis'import{SettingsProvider,SettingsNamespace}from'@deepseek-ai/dsh-settings'importzfrom'@deepseek-ai/schemastery'constctx=newContext()constsource={test:{qux:"user_qux"}}ctx.plugin(MemorySettingsProvider,source)ctx.plugin(ctx=>ctx.on("settings/updated",(ns,next,prev,source)=>console.log(`detect settings' change: ns:${ns}source:${source}prev:${JSON.stringify(prev,null,2)}next:${JSON.stringify(next,null,2)}`)))ctx.inject(["settings"],ctx=>{ctx.settings.register("test",ConfigSchema,{base:{baz:"base_baz",qux:"base_qux"},applies:'live'});(source.testasany)["baz"]="222";(ctx.settingsasMemorySettingsProvider).notifyChange()ctx.settings.update("test",{foobar:{foo:"333"}})})

输出:

detect settings' change: ns: test source: provider prev: { "foobar": { "foo": "schema_default_foo", "bar": "schema_default_bar" }, "baz": "base_baz", "qux": "user_qux" } next: { "foobar": { "foo": "schema_default_foo", "bar": "schema_default_bar" }, "baz": "222", "qux": "user_qux" }
detect settings' change: ns: test source: update prev: { "foobar": { "foo": "schema_default_foo", "bar": "schema_default_bar" }, "baz": "222", "qux": "user_qux" } next: { "foobar": { "foo": "333", "bar": "schema_default_bar" }, "baz": "222", "qux": "user_qux" }

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询