尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Webiny Headless CMS 过滤器注册表 DI 重构:以依赖注入替代 PluginsContainer 透传

发布时间:2026/9/29 2:51:56

资讯中心
01
ARTICLE

Webiny Headless CMS 过滤器注册表 DI 重构:以依赖注入替代 PluginsContainer 透传

Webiny Headless CMS 过滤器注册表 DI 重构:以依赖注入替代 PluginsContainer 透传
CMS后端前端【免费下载链接】webiny-jsOpen-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.项目地址https://gitcode.com/gh_mirrors/we/webiny-js点击查看免费下载Webinywebiny-js在其 SQL / PostgreSQL OpenSearch 存储层中将条目Entry过滤与排序所用的 4 类插件从全局PluginsContainer按类型查找 逐层透传改为4 个 DI 可解析的注册表Registry 容器单例装配。本文基于本次重构的交接文档与设计文档结合仓库中api-headless-cms-storage、api-headless-cms-sql、api-headless-cms-pg-os三个包的实际源码完整讲解这套注册表 处理器工厂Handler Factory Feature 装配的架构模式四种注册表的抽象与实现、七个处理器工厂、FilterRegistriesFeature的注册细节、消费函数createFields/createExpressions/filter/sort的签名演进以及为何在迁移 SQL/PG-OS 后端时保留旧插件类不动。一、重构背景PluginsContainer透传的痛点在重构之前Headless CMS 的条目列表查询依赖api-headless-cms-storage提供的 4 类插件它们全部通过PluginsContainer.byType()按类型字符串查找并沿着调用链一路透传HeadlessCmsPgOsFeature → createEntriesStorageOperations (pg-os) → createSqlEntriesStorageOperations (sql) → listEntries → createFields / filter / sort参与条目过滤/排序的 4 类插件如下摘自设计文档 2026-07-20-cms-storage-filter-registries-di-design.mdPlugin 类类型字符串键Keyed by消费方CmsEntryFieldFilterPathPlugincms-field-filter-pathfieldTypecreateFields()CmsFieldFilterValueTransformPlugincms-field-filter-value-transformfieldTypecreateFields()、createExpressions()CmsEntryFieldFilterPlugincms.dynamodb.entry.field.filterfieldTypecreateExpressions()CmsEntryFieldSortingPlugincms.entry.field.sorting列表不按键extractSort()该方案有三个明显问题隐式依赖函数签名中到处携带plugins: PluginsContainer真正的依赖哪些类型的插件需要深入函数体才能看清重复注册api-headless-cms-sql/src/index.ts与api-headless-cms-pg-os/src/features/HeadlessCmsPgOsFeature.ts各自注册了完全相同的一套插件运行期才可发现错误byType()查找不到时只能抛运行期异常无法借助编译期类型系统保证注册表里一定存在对应处理器。重构目标明确将上述 4 类插件替换为 4 个 DI 可解析的注册表抽象由单一FilterRegistriesFeature在容器装配期完成填充消费函数改为直接接收注册表接口并从整条参数链中移除plugins: PluginsContainer。二、四个注册表抽象命名空间 Abstraction模式每个注册表抽象都遵循createAbstraction() 同名 namespace 导出.Interface/.Handler类型的 DI 惯例且一个抽象/实现独占一个文件。四个抽象全部位于packages/api-headless-cms-storage/src/features/name/abstractions.ts。2.1 FieldFilterPathRegistry字段过滤路径负责根据字段类型生成过滤路径例如带settings.path的 plainObject 字段接口定义见 abstractions.tsimport { Abstraction } from webiny/di; import type { CmsModelField } from webiny/api-headless-cms/types/index.js; import type { CreatePathCallableParams } from ../../plugins/CmsEntryFieldFilterPathPlugin.js; export interface IFieldFilterPathHandler { canUse(field: PickCmsModelField, fieldId | type, parents: string[]): boolean; createPath(params: CreatePathCallableParams): string; } export interface IFieldFilterPathRegistry { register(fieldType: string, handler: IFieldFilterPathHandler): void; get(fieldType: string): IFieldFilterPathHandler | undefined; } export const FieldFilterPathRegistry new AbstractionIFieldFilterPathRegistry( Cms/Storage/FieldFilterPathRegistry ); export namespace FieldFilterPathRegistry { export type Interface IFieldFilterPathRegistry; export type Handler IFieldFilterPathHandler; }2.2 FieldFilterValueTransformRegistry过滤值变换负责在比较前对过滤值做变换最典型的是 datetime 字段的时间戳化定义见 abstractions.ts。接口与路径注册表同构register(fieldType, handler)get(fieldType)Handler 只暴露transform(params)。2.3 FieldFilterCreateRegistry过滤条件构造负责把where子句中的某个键如title_contains解析为一个可执行的过滤器定义见 abstractions.ts。相比前两个注册表它多了getDefault()export interface IFieldFilterCreateHandler { create( params: IFieldFilterCreateParams ): null | IFieldFilterCreateResult | IFieldFilterCreateResult[]; } export interface IFieldFilterCreateRegistry { register(fieldType: string, handler: IFieldFilterCreateHandler): void; get(fieldType: string): IFieldFilterCreateHandler | undefined; getDefault(): IFieldFilterCreateHandler; // 返回 * 条目 }递归查找的细节objectFilterCreate需要把嵌套对象字段的过滤委托给其他处理器例如默认处理器。因此IFieldFilterCreateParams中不仅携带transformRegistry: FieldFilterValueTransformRegistry.Interface还携带getHandler: (fieldType: string) IFieldFilterCreateHandler回调——它由createExpressions()在调用时注入供嵌套处理器递归解析。2.4 FieldSortingRegistry排序处理器与前三个按 fieldType 键控的注册表不同排序注册表不按键find()采用逆序遍历后注册者优先完全等价于旧实现plugins.byType().reverse().find()定义见 abstractions.tsexport interface IFieldSortingHandler { canUse(params: IFieldSortingCanUseParams): boolean; createSort(params: IFieldSortingCreateParams): IFieldSortingResult; } export interface IFieldSortingRegistry { register(handler: IFieldSortingHandler): void; find(params: IFieldSortingCanUseParams): IFieldSortingHandler | undefined; }其中IFieldSortingResult输出valuePath取值路径、reverse是否为 DESC、fieldId与field供sort()完成内存排序。三、四个注册表实现Map / 数组背书的轻量类四个实现类与抽象同文件目录存放均直接实现对应的.InterfaceFieldFilterPathRegistryImplFieldFilterPathRegistry.ts内部Mapstring, Handlerregister()/get()即map.set/map.getFieldFilterValueTransformRegistryImpl同上结构FieldFilterCreateRegistryImplFieldFilterCreateRegistry.ts在 Map 基础上增加getDefault()——读取*键缺失时抛出WebinyErrorcodeMISSING_DEFAULT_HANDLERFieldSortingRegistryImplFieldSortingRegistry.ts内部为Handler[]数组find()从数组尾部向前遍历找到第一个canUse(params)返回 true 的处理器。以FieldFilterCreateRegistryImpl为例摘自 FieldFilterCreateRegistry.tsexport class FieldFilterCreateRegistryImpl implements FieldFilterCreateRegistry.Interface { private readonly handlers new Mapstring, FieldFilterCreateRegistry.Handler(); public register(fieldType: string, handler: FieldFilterCreateRegistry.Handler): void { this.handlers.set(fieldType, handler); } public get(fieldType: string): FieldFilterCreateRegistry.Handler | undefined { return this.handlers.get(fieldType); } public getDefault(): FieldFilterCreateRegistry.Handler { const handler this.handlers.get(*); if (!handler) { throw new WebinyError( No default filter create handler registered., MISSING_DEFAULT_HANDLER ); } return handler; } }四、七个处理器工厂从 Plugin 工厂中提取纯逻辑原插件工厂返回的是带Plugin类包装的实例新设计将其逻辑抽成返回纯Handler接口的工厂函数放在packages/api-headless-cms-storage/src/handlers/目录该目录属于工厂函数因此不放入 feature 文件夹处理器工厂来源插件工厂用途createPlainObjectPathHandler()createPlainObjectPathPlugin()settings.path路径生成createLocationFolderIdPathHandler()createLocationFolderIdPathPlugin()ACO location 的folderId特殊路径createDatetimeTransformHandler()createDatetimeTransformValuePlugin()datetime/time 值转时间戳createDefaultFilterCreateHandler()createDefaultFilterCreate()*默认过滤构造createRefFilterCreateHandler()createRefFilterCreate()ref 字段过滤createObjectFilterCreateHandler()objectFilterCreate()嵌套对象字段过滤createSearchableJsonFilterCreateHandler()searchableJsonFilterCreate()searchable-json 字段过滤例如 plainObjectPathHandler.tsexport const createPlainObjectPathHandler (): FieldFilterPathRegistry.Handler { return { canUse: () true, createPath: ({ field }) { const { path } field.settings || {}; if (!path) { throw new WebinyError(Missing path settings value., FIELD_SETTINGS_ERROR, { field }); } return path; } }; };而 datetimeTransformHandler.ts 则完整继承了旧的变换逻辑settings.type time时走transformTime()把HH:mm:ss[.ms]转成毫秒数否则走transformDateTime()ISO 字符串 / Date 实例转getTime()解析失败返回null并告警。五、FilterRegistriesFeature容器装配的单一入口所有处理器在容器装配期由FilterRegistriesFeature一次性注册实际源码见 FilterRegistriesFeature.tsimport { createFeature } from webiny/feature/api/index.js; // ... 注册表抽象、实现与处理器工厂的 imports ... export const FilterRegistriesFeature createFeature({ name: cms.storage.filterRegistries, register: container { const pathRegistry new FieldFilterPathRegistryImpl(); pathRegistry.register(plainObject, createPlainObjectPathHandler()); pathRegistry.register(text, createLocationFolderIdPathHandler()); container.registerInstance(FieldFilterPathRegistry, pathRegistry); const transformRegistry new FieldFilterValueTransformRegistryImpl(); transformRegistry.register(datetime, createDatetimeTransformHandler()); container.registerInstance(FieldFilterValueTransformRegistry, transformRegistry); const filterCreateRegistry new FieldFilterCreateRegistryImpl(); filterCreateRegistry.register(*, createDefaultFilterCreateHandler()); filterCreateRegistry.register(ref, createRefFilterCreateHandler()); filterCreateRegistry.register(object, createObjectFilterCreateHandler()); filterCreateRegistry.register(searchable-json, createSearchableJsonFilterCreateHandler()); container.registerInstance(FieldFilterCreateRegistry, filterCreateRegistry); const sortingRegistry new FieldSortingRegistryImpl(); container.registerInstance(FieldSortingRegistry, sortingRegistry); } });关键决策一registerInstance而非registerFactory本次重构最核心的决策记录在交接文档 2026-07-21-filter-registries-di.md是注册表一律用container.registerInstance()注册单例而不是registerFactory().inSingletonScope()。原因在于registerFactory在容器每次resolve()时都会重新执行工厂函数创建新实例——即使声明了单例作用域注册表内部累积的Map/数组状态也会被重复初始化导致已注册处理器丢失。registerInstance保证整个容器生命周期内只存在一个注册表实例。设计文档中的示例曾使用registerFactory(...).inSingletonScope()写法实际落地的实现已统一改为registerInstance以交接文档与仓库源码为准。关键决策二双端注册与幂等性FilterRegistriesFeature同时注册到两个存储后端api-headless-cms-sql/src/index.ts 的HeadlessCmsSqlFeature中FilterRegistriesFeature.register(container)HeadlessCmsPgOsFeature.ts 中同样调用FilterRegistriesFeature.register(container)。由于采用registerInstance单例语义即使 SQL 与 PG-OS 两个 Feature 在同一个容器中先后注册注册表实例也只会被创建一次、处理器不会被重复填充天然幂等。关键决策三FieldSortingRegistry注册为空交接文档明确指出SQL / PG-OS 后端不使用排序插件只有 ddb 后端使用因此FieldSortingRegistry注册时不填充任何处理器。sort()走回退逻辑——若find()未命中处理器则使用field.createPath()生成默认路径reverse order DESC。六、消费函数重构从plugins到注册表参数api-headless-cms-storage中的 4 个消费函数签名全部变更参数替换关系如下函数旧参数新参数createFields()pluginspathRegistry、transformRegistrycreateExpressions()pluginsfilterCreateRegistry、transformRegistryfilter()pluginsfilterCreateRegistry、transformRegistrysort()/extractSort()pluginssortingRegistry以 createFields.ts 为例新签名如下interface Params { fields: CmsModelField[]; pathRegistry: FieldFilterPathRegistry.Interface; transformRegistry: FieldFilterValueTransformRegistry.Interface; }createFieldCollection内部不再调用getMappedPlugins()而是直接transformRegistry.get(fieldType)/pathRegistry.get(fieldType)取处理器并注入createPath/transform闭包。createExpressions()中的getFilterCreatePlugin回调也替换为getHandler解析逻辑为filterCreateRegistry.get(getBaseFieldType(field)) || filterCreateRegistry.getDefault()找不到时抛出WebinyError(MISSING_FILTER_CREATE_HANDLER)。参数链清理plugins: PluginsContainer从以下位置全部移除CreateEntriesStorageOperationsParamsapi-headless-cms-sql/src/operations/entry/index.ts与api-headless-cms-pg-os/src/operations/entry/index.tsSqlStorageOperationsFactoryParamsapi-headless-cms-sql/src/types.ts与PgOsStorageOperationsFactoryParamsHeadlessCmsPgOsFeature.tscreateSqlStorageOperations/SqlStorageOperationsFactoryImpl.create()、createPgOsStorageOperations/PgOsStorageOperationsFactoryImpl.create()。listEntries()从container中一次性解析全部注册表后交给各函数const pathRegistry container.resolve(FieldFilterPathRegistry); const transformRegistry container.resolve(FieldFilterValueTransformRegistry); const filterCreateRegistry container.resolve(FieldFilterCreateRegistry); const sortingRegistry container.resolve(FieldSortingRegistry);SQL 后端还顺带移除了对webiny/plugins的未使用依赖。七、向后兼容旧插件体系完整保留为避免破坏 DynamoDB 与 DynamoDB OpenSearch 后端以下内容保持不变详见设计文档 What Stays Unchanged 一节4 个 Plugin 类CmsEntryFieldFilterPathPlugin、CmsFieldFilterValueTransformPlugin、CmsEntryFieldFilterPlugin、CmsEntryFieldSortingPlugin见 plugins 目录——ddb/ddb-es 仍在使用getMappedPlugins()工具函数mapPlugins.ts——ddb/ddb-es 仍在使用原有插件工厂函数createFilterCreatePlugins()、createPlainObjectPathPlugin()等见 filtering/plugins——ddb/ddb-es 仍会调用PluginsContainer类本身——unchanged。这意味着重构是新老并存、逐后端迁移的渐进式过程SQL / PG-OS 后端切换到注册表体系DDB 系列后端暂时继续走插件体系等全部后端迁移完成后插件类与工厂函数才可考虑删除。八、文件布局与测试验证最终文件清单api-headless-cms-storage新增抽象层src/features/fieldFilterPath/abstractions.ts、src/features/fieldFilterValueTransform/abstractions.ts、src/features/fieldFilterCreate/abstractions.ts、src/features/fieldSorting/abstractions.ts实现层src/features/fieldFilterPath/FieldFilterPathRegistry.ts、src/features/fieldFilterValueTransform/FieldFilterValueTransformRegistry.ts、src/features/fieldFilterCreate/FieldFilterCreateRegistry.ts、src/features/fieldSorting/FieldSortingRegistry.ts装配层src/features/FilterRegistriesFeature.ts处理器层src/handlers/{plainObjectPathHandler, locationFolderIdPathHandler, datetimeTransformHandler, defaultFilterCreateHandler, refFilterCreateHandler, objectFilterCreateHandler, searchableJsonFilterCreateHandler}.ts修改src/filtering/fields/createFields.ts、src/filtering/expressions/createExpressions.ts、src/filtering/filter.ts、src/filtering/sort.ts、src/filtering/fields/extractSort.ts、src/index.ts导出 4 个注册表抽象与FilterRegistriesFeature、package.json新增webiny/feature依赖。api-headless-cms-sql/api-headless-cms-pg-ossrc/operations/entry/index.ts、src/types.ts、src/index.ts/src/features/HeadlessCmsPgOsFeature.ts全部移除plugins透传并接入FilterRegistriesFeature。验证结果交接文档记录的验证状态分支bruno/feat/api-postgres-to-os16 个提交领先于origin/next尚未推送测试api-headless-cms-storage15 项通过、api-headless-cms-pg-os9 项通过构建通过、lint/format 干净、工作树干净。现有测试不直接调用新的注册表接口而是直接测试未变化的插件工厂因此重构期间测试保持全绿——这也正是插件层保持不动策略带来的额外红利。九、后续路线What might come next交接文档列出的后续步骤可作为继续跟进该重构的路线图迁移 ddb 与 ddb-es 包将同样的注册表模式应用到 DynamoDB 后端模式已在 SQL/PG-OS 验证删除旧插件类全部后端迁移完成后移除 4 个 Plugin 类与对应工厂函数以及getMappedPlugins()完整集成测试以WEBINY_STORAGEpg-os配合真实 OpenSearch 跑全量 CMS 集成测试WAL 监听基础设施为 PostgreSQL → OpenSearch 同步搭建 WAL 监听推送与 PR将 16 个提交推送并创建 Pull Request。十、总结这套重构为 Webiny 的多后端存储架构提供了一个可复制的 DI 范式用命名空间抽象 Map/数组实现 处理器工厂 Feature 装配四件套替换类型字符串 全局容器 逐层透传的插件体系。其价值在于依赖关系显式化函数签名直接声明所需注册表、装配集中化FilterRegistriesFeature一处完成全部注册、编译期可检查AbstractionInterface提供强类型、以及通过registerInstance保证单例状态不丢失。对于正在为大型代码库引入 DI 容器、或计划将插件式架构迁移到注册表式架构的团队本案例提供了完整且经过测试验证的落地样本——相关实现细节可继续在 api-headless-cms-storage/src 中深入阅读。赞分享CMS后端前端【免费下载链接】webiny-jsOpen-source, self-hosted CMS platform on AWS serverless (Lambda, DynamoDB, S3). TypeScript framework with multi-tenancy, lifecycle hooks, GraphQL API, and AI-assisted development via MCP server. Built for developers at large organizations.项目地址https://gitcode.com/gh_mirrors/we/webiny-js点击查看免费下载相关推荐Webiny OpenSearch 插件体系迁移实战从 PluginsContainer 到 DI 抽象与注册表Webiny OpenSearch 插件体系迁移实战从 PluginsContainer 到 DI 抽象与注册表 导读 本文以 Webiny 开源仓库中 apCMS后端前端Webiny CMS 存储操作全量 DI 迁移实录api-headless-cms 四大适配器的按方法注入重构Webiny CMS 存储操作全量 DI 迁移实录api headless cms 四大适配器的按方法注入重构 导读 本文基于 Webiny 开源仓库中 doCMS后端前端Webiny Headless CMS 存储层重构移除废弃的 register*StorageOperations 包装器全面落地 DI Feature 注册模式Webiny Headless CMS 存储层重构移除废弃的 register StorageOperations 包装器全面落地 DI Feature 注CMS后端前端上一篇EdgeRemover3分钟专业级Edge卸载方案深度解析下一篇极速GitHub访问高效浏览器插件完整使用手册创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。