Webiny 的 OpenSearch AWS 拆分方案:将 `@webiny/api-opensearch` 拆分为基础包与 AWS 专属包
发布时间:2026/9/29 10:27:14来源:尧图网络
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 开源仓库中的设计与实施计划文档docs/.bruno/plans/2026-07-17-opensearch-aws-split/系列及配套 specdocs/.bruno/specs/2026-07-17-api-opensearch-aws-split-design.md完整讲解如何把webiny/api-opensearch中内嵌的 AWS SigV4 签名逻辑拆分为独立的新包webiny/api-opensearch-aws。读者将理解拆分动机、两个包的职责边界、createAwsOpenSearchClient包装函数的实现细节、DI Feature 的替换机制、消费方事件处理器、AWS 项目模板、webiny/webiny聚合包的迁移路径以及整套 9 步计划的依赖排序与验证方式——这些内容已在本仓库中落地可作为直接参考的实现样例。一、问题背景为什么要把 OpenSearch 客户端包拆开在 Webiny 的 AWS 无服务器架构中Headless CMS 的搜索能力依赖 OpenSearch而客户端创建集中在 client.ts。拆分之前webiny/api-opensearch在client.ts中无条件引入 AWS SigV4 签名逻辑import { AwsSigv4Signer } from opensearch-project/opensearch/aws;这带来两个现实问题非 AWS 部署被拖累Webiny 同时支持 PostgreSQL 自托管 OpenSearchPGOS 变体等非 AWS 场景。它们并不需要 SigV4却被迫把 AWS SDK 相关依赖打进了构建产物增大了 Lambda 包体与冷启动开销。职责混杂一个通用客户端包里同时包含标准客户端逻辑和 AWS 平台专属逻辑任何改动都会影响所有消费方。设计文档给出的方案也是仓库中已实施的方案是一拆为二webiny/api-opensearch基础包标准 OpenSearch 客户端、全部既有 DI Feature、工具函数、测试辅助。零 AWS 依赖。webiny/api-opensearch-aws新包在基础客户端之上叠加 SigV4 签名并提供一个用于按需创建客户端的 DI Factory 替换实现。拆分后的架构关系可概括为基础包提供裸客户端 抽象 Feature 注册AWS 包提供签名包装 Factory 替换消费方按需引入。二、总体执行计划9 步依赖图计划由 9 个相互关联的子计划组成整体依赖顺序如下来自 00-overview.md01-base-client-cleanup │ ▼ 02-new-package-scaffold ──► 03-aws-client-wrapper │ ▼ 04-aws-factory-feature │ ▼ 05-aws-package-exports │ ▼ 06-consumer-event-handler (depends on 05) 07-consumer-template (depends on 05) 08-consumer-webiny-reexport (depends on 05) │ ▼ 09-build-verify (depends on all above)可并行执行的部分01与02相互独立可并行一个做减法一个做加法06、07、08三个消费方改造都只依赖05导出结构定稿彼此可并行。必须串行的部分03依赖02新包目录与 tsconfig 先就位04依赖03Factory 内部调用包装函数05依赖04导出需要包含 Feature09依赖前面全部最终全量构建验证。这一依赖设计把最长路径控制在02 → 03 → 04 → 05 → 09其余环节可多路并行非常适合多人协作的 PR 拆分。三、Plan 01基础包清理移除 SigV4 逻辑涉及文件packages/api-opensearch/src/client.ts清理任务非常聚焦只有两步删除删除第 5 行import { AwsSigv4Signer } from opensearch-project/opensearch/aws;删除整个if (!clientOptions.auth) { ... }的 SigV4 回退代码块其余必须原样保留客户端缓存、错误处理、Client重导出、类型定义。清理后的createOpenSearchClient语义变为不提供 auth 时创建未签名unsigned客户端适用于本地开发环境或关闭安全认证的自托管 OpenSearch。清理后的目标实现与当前仓库 client.ts 一致export const createOpenSearchClient (options: OpenSearchClientOptions): Client { const key createClientKey(options); const existing clients.get(key); if (existing) { return existing; } const { endpoint, node, ...rest } options; const clientOptions: ClientOptions { node: endpoint || node, ...rest }; try { const client new Client(clientOptions); clients.set(key, client); return client; } catch (ex) { const data { error: ex, node: endpoint || node, ...rest, auth: undefined }; console.error(data); throw new WebinyError(Could not connect to OpenSearch., OPENSEARCH_CLIENT_ERROR, data); } };源码层面的几个保留细节客户端缓存createClientKey将 options 序列化后做 SHA-256 哈希crypto.createHash(sha256)相同配置复用同一个Client实例避免重复建立连接。endpoint归一化endpoint是 Webiny 的扩展字段node: endpoint || node保证调用方无论传endpoint还是原生node都能正确构建ClientOptions。错误包装连接失败时统一抛出WebinyError错误码为OPENSEARCH_CLIENT_ERROR并在 data 中附带error、node与其余 options 便于排查。验证方式yarn build -p webiny/api-opensearch 21 | tail -30必须编译通过且api-opensearch内除client.ts外无其他文件改动。四、Plan 02新包骨架搭建新包webiny/api-opensearch-aws位于 packages/api-opensearch-aws/。目标目录结构packages/api-opensearch-aws/ ├── package.json ├── tsconfig.json ├── src/ │ ├── index.ts # barrel公开 API │ ├── createAwsOpenSearchClient.ts # SigV4 包装函数 │ ├── exports/ │ │ └── api/ │ │ └── opensearchAws.ts # 规范消费路径函数 Feature │ └── features/ │ └── AwsOpenSearchClientFactory/ │ ├── AwsOpenSearchClientFactory.ts # 实现内部 │ └── feature.ts # DI Feature 注册package.json 要点包名webiny/api-opensearch-aws版本0.0.0遵循 monorepo 约定依赖opensearch-project/opensearch版本与api-opensearch保持一致用于/aws子路径的AwsSigv4Signer、webiny/api-opensearch、webiny/error、webiny/feature提供exports字段包含./exports/api/opensearchAws.js规范路径结构参考已有小包如api-opensearch。当前仓库实际落地的 package.json 显示依赖还包括webiny/api、webiny/aws-sdk、webiny/db-dynamodb后续扩展了 DDB→OS 实体辅助与测试工具说明该包已随功能演进超越最初骨架但核心依赖opensearch 客户端、基础包、error、feature与计划完全一致。tsconfig.json 要点继承基础 tsconfig将api-opensearch作为 project reference 引用使用~/路径别名指向src/整体遵循packages/api-opensearch/tsconfig.json的模式。五、Plan 03AWS 客户端包装函数文件packages/api-opensearch-aws/src/createAwsOpenSearchClient.ts这是整个拆分的核心把旧client.ts中第 37–64 行的 SigV4 逻辑抽取为独立包装函数。当前仓库实现如下import { AwsSigv4Signer } from opensearch-project/opensearch/aws; import { createOpenSearchClient, type OpenSearchClientOptions, type Client } from webiny/api-opensearch; import WebinyError from webiny/error; export const createAwsOpenSearchClient (options: OpenSearchClientOptions): Client { if (options.auth) { return createOpenSearchClient(options); } const region process.env.AWS_REGION; if (!region) { throw new WebinyError(Missing AWS_REGION environment variable., MISSING_AWS_REGION); } return createOpenSearchClient({ ...options, ...AwsSigv4Signer({ region, service: es, getCredentials: () { const accessKeyId process.env.AWS_ACCESS_KEY_ID; const secretAccessKey process.env.AWS_SECRET_ACCESS_KEY; const sessionToken process.env.AWS_SESSION_TOKEN; if (!accessKeyId || !secretAccessKey) { throw new WebinyError( Missing AWS credentials (AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY)., MISSING_AWS_CREDENTIALS ); } return Promise.resolve({ accessKeyId, secretAccessKey, sessionToken }); } }) }); };行为语义与参数说明场景行为options.auth存在原样透传给基础createOpenSearchClient走 Basic Auth用于本地/自托管 OpenSearchoptions.auth不存在从环境变量读取 AWS 凭证注入AwsSigv4Signer后再调用基础客户端AWS 托管 OpenSearch/ES涉及的环境变量AWS_REGION必需缺失时抛出WebinyError错误码MISSING_AWS_REGIONAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY必需任一缺失抛出WebinyError错误码MISSING_AWS_CREDENTIALSAWS_SESSION_TOKEN可选临时凭证场景直接随凭证返回service: esSigV4 签名服务标识对应 Amazon OpenSearch Service / Elasticsearch Service。错误码沿用旧client.ts中的约定保证向后兼容。六、Plan 04AWS Factory 的 DI Feature目录packages/api-opensearch-aws/src/features/AwsOpenSearchClientFactory/该 Feature 的作用是在容器中注册时替换基础包的OpenSearchClientFactory绑定使按需创建客户端走 SigV4 路径。实现类当前仓库实现见 AwsOpenSearchClientFactory.ts与基础包 OpenSearchClientFactory.ts 逐行对应class AwsOpenSearchClientFactoryImpl implements OpenSearchClientFactoryAbstraction.Interface { public getClient(params: OpenSearchClientOptions): Client { if (!params.endpoint !params.node !params.nodes) { throw new Error( OpenSearch client requires an endpoint, nodes or node to be specified. ); } return createAwsOpenSearchClient(params); } } export const AwsOpenSearchClientFactory OpenSearchClientFactoryAbstraction.createImplementation({ implementation: AwsOpenSearchClientFactoryImpl, dependencies: [] });与基础实现的唯一差异内部调用createAwsOpenSearchClient带 SigV4而非createOpenSearchClient。参数校验逻辑endpoint/node/nodes至少提供一个与接口完全一致。OpenSearchClientFactory抽象定义位于 abstraction.ts通过createAbstractionIOpenSearchClientFactory(OpenSearch/ClientFactory)创建Interface仅要求一个getClient(params): Client方法。Feature 注册当前仓库实现见 feature.tsimport { createFeature } from webiny/feature/api/index.js; import { AwsOpenSearchClientFactory } from ./AwsOpenSearchClientFactory.js; export const AwsOpenSearchClientFactoryFeature createFeature({ name: opensearch.aws.clientFactory, register(container) { container.register(AwsOpenSearchClientFactory).inSingletonScope(); } });Feature 名为opensearch.aws.clientFactory与基础包 feature.ts 中的opensearch.internal.clientFactory形成区分。两者均以单例inSingletonScope方式注册各自实现——由于注册到同一抽象OpenSearch/ClientFactory后注册的 AWS 实现会覆盖基础绑定这正是替换而非装饰的设计意图。七、Plan 05导出结构与规范消费路径涉及文件src/index.ts与src/exports/api/opensearchAws.ts该步遵循minimal barrel exports最小化桶导出原则——只导出外部消费者真正需要的内容内部实现细节不泄漏。src/index.tsbarrel计划要求只导出createAwsOpenSearchClient一个公共 API 函数Feature 不进入 barrel必须通过规范路径引入。当前仓库 index.ts 在此基础上还导出了AwsOpenSearchClientFactoryFeature与 DDB 实体/表辅助createOpenSearchEntity、createOpenSearchTable等说明导出面已按后续功能需求扩展但功能实现保持内部这一原则未变——AwsOpenSearchClientFactory实现类本身没有从任何公开入口导出。src/exports/api/opensearchAws.ts规范消费路径同时提供函数与 DI Featureexport { createAwsOpenSearchClient } from ~/createAwsOpenSearchClient.js; export { AwsOpenSearchClientFactoryFeature } from ~/features/AwsOpenSearchClientFactory/feature.js;package.json的exports字段需保证规范路径可被解析计划中的示例为{ exports: { .: ./src/index.ts, ./exports/api/opensearchAws.js: ./src/exports/api/opensearchAws.ts } }当前仓库实际使用./index.js与./*的通配模式构建产物路径但规范路径的解析能力一致。基础包对应的规范入口是 exports/api/opensearch.ts它导出createOpenSearchClient、OpenSearchClient、OpenSearchClientFactory、各类 Operator/Field/Index 抽象——AWS 包的导出结构正是照此模式设计的。八、消费方迁移Plan 06–08拆分完成后仓库 55 个引用webiny/api-opensearch的导入点中只有 2 处需要改动事件处理器与 AWS 项目模板。其余 53 处各类 Feature、工具、测试辅助均留在基础包保持不动。8.1 消费方一事件处理器webiny/api-event-handler-aws-ddb-os文件createWebinyApiHandler.ts改动共 4 行外加package.json增加依赖webiny/api-opensearch-aws- import { createOpenSearchClient, type OpenSearchClientOptions } from webiny/api-opensearch; import { type OpenSearchClientOptions } from webiny/api-opensearch; import { createAwsOpenSearchClient, AwsOpenSearchClientFactoryFeature } from webiny/api-opensearch-aws;openSearchClientFromEnv中createOpenSearchClient(...)→createAwsOpenSearchClient(...)registerRootStorage中OpenSearchClientFactoryFeature.register(container)→AwsOpenSearchClientFactoryFeature.register(container)。保持不变的部分当前仓库 createWebinyApiHandler.ts 可验证OpenSearchClientFeature的导入与注册opensearch.internal.client抽象注册实际客户端实例OpenSearchQueryBuilderOperatorFeature、OpenSearchFieldFeature、OpenSearchIndexFeature的导入与注册openSearchClient配置类型与OPENSEARCH_*环境变量读取逻辑。该处理器从OPENSEARCH_ENDPOINT、OPENSEARCH_USERNAME、OPENSEARCH_PASSWORD构建 options当用户名/密码存在时走 Basic Auth自托管场景缺失时由createAwsOpenSearchClient自动回退到 SigV4AWS 托管场景——这一双模式正是包装函数存在的意义。注册顺序上AWS Factory 先注册随后OpenSearchQueryBuilderOperatorFeature等其余核心 Feature 照常注册最终 DDBES 的 CMS 存储工厂从容器中解析这些抽象。8.2 消费方二AWS 项目模板webiny/project-aws文件packages/project-aws/_templates/extensions/OpenSearch/coreDdbToEsHandler/dynamoToElastic/src/index.ts该模板是 DynamoDB → OpenSearch 流式同步处理器由 core 表的 DynamoDB Stream 触发当前实现见 index.ts- import { createOpenSearchClient, type OpenSearchClientOptions } from webiny/api-opensearch; import { type OpenSearchClientOptions } from webiny/api-opensearch; import { createAwsOpenSearchClient } from webiny/api-opensearch-aws;使用处由createOpenSearchClient(clientOptions)改为createAwsOpenSearchClient(clientOptions)。模板同样保留有用户名密码则 Basic Auth、否则 SigV4的双模式逻辑最终将客户端注入createDdbToOpenSearchStreamHandler。注意模板文件通常不直接声明依赖脚手架生成到用户项目后再解析依赖是否需要在project-aws/package.json中登记webiny/api-opensearch-aws需遵循模板依赖的既有处理模式。8.3 消费方三聚合包webiny/webiny重导出涉及文件packages/webiny/src/api/opensearchAws.ts新建与packages/webiny/package.json目的是让使用webiny/webiny规范导入路径的消费者也能拿到 AWS 包能力// packages/webiny/src/api/opensearchAws.ts export { createAwsOpenSearchClient } from webiny/api-opensearch-aws; export { AwsOpenSearchClientFactoryFeature } from webiny/api-opensearch-aws/exports/api/opensearchAws.js;同时packages/webiny/package.json增加依赖webiny/api-opensearch-aws并仿照既有./api/opensearch.js条目新增./api/opensearchAws.js的 exports 映射packages/webiny/tsconfig.json增加对api-opensearch-aws的 project reference。需要说明截至当前仓库状态packages/webiny/src/api/下仅有 opensearch.ts基础包重导出opensearchAws.ts尚未出现说明该步属于计划中的待执行项或未合入当前分支——这正好印证了计划文档中08 依赖 05、可与其他消费方并行的执行编排。九、设计决策与不变项关键设计决策基础包保留原名api-opensearch虽然语义上它已变成服务器通用基础包但改名api-opensearch-server需要更新 55 个导入点功能上没有任何收益故不做重命名。无 auth 未签名客户端本地/开发环境的 OpenSearch关闭安全认证直接用基础包即可工作AWS 环境由专属包在之上叠加 SigV4。包装函数 DI Feature双管齐下既覆盖急切创建事件处理器直接调用createAwsOpenSearchClient又覆盖按需创建Factory 从 DI 容器解析比纯 DI 方案更显式、更灵活。Factory 采用替换而非装饰AwsOpenSearchClientFactoryFeature直接为OpenSearchClientFactory抽象注册自己的实现并覆盖基础绑定。对于单一覆盖场景替换比装饰器链更简单。保持不变的资产全部 DI 抽象OpenSearchClient、OpenSearchClientFactory、OpenSearchField、OpenSearchIndex、OpenSearchQueryBuilderOperator留在基础包registerOpenSearchCore留在基础包全部测试辅助留在基础包如createTestOpenSearchClient、registerOpenSearchCoreForTests它们直接new Client()不涉及签名全部工具函数sort、where、limit、normalize、cursors、indices、waitUntilHealthy 等留在基础包基础包的规范导出路径exports/api/opensearch.ts不变。这一点在源码结构中清晰可见packages/api-opensearch/src 下features/、operations/、testing/、utils/、indexConfiguration/均完好保留client.ts中已无任何AwsSigv4Signer痕迹。十、构建验证与收尾Plan 09全部子计划完成后进行全量构建验证。各步骤的验证命令统一为yarn build -p webiny/api-opensearch 21 | tail -30 yarn build -p webiny/api-event-handler-aws-ddb-os 21 | tail -30 yarn build -p webiny/webiny 21 | tail -30tail -30用于截取构建输出的尾部快速聚焦于错误信息或编译完成摘要。最终验收清单webiny/api-opensearch编译通过且除client.ts外无任何文件改动webiny/api-opensearch-aws的createAwsOpenSearchClient类型解析正确依赖基础包导出的类型事件处理器package.json新增依赖后 import 路径可解析仅 4 行代码变更webiny/webiny的./api/opensearchAws.js导出路径解析正确模板文件无明显的类型错误。结语拆分带来的架构收益从设计文档与仓库现状看这次拆分的核心收益有三点依赖边界清晰化非 AWS 部署PGOS、自托管 OpenSearch不再背负 AWS SDK 依赖构建产物更精简扩展点显式化AWS 专属能力SigV4 签名、凭证获取、Factory 替换收敛到单一包内后续新增 AWS 相关行为无需触碰基础包迁移成本极低55 个导入点仅 2 处需要修改其余全部通过留在基础包天然兼容配合可并行的计划编排整体风险可控。对于希望在自研项目中复刻这一模式的团队本仓库的 packages/api-opensearch 与 packages/api-opensearch-aws 两个包、createWebinyApiHandler.ts 的双模式客户端构造以及全套 计划文档构成了平台通用层与云厂商专属层分离的完整参考实现。赞分享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 拆分 api-opensearch-aws将 AWS SigV4 签名从基础 OpenSearch 客户端解耦Webiny 拆分 api opensearch aws 将 AWS SigV4 签名从基础 OpenSearch 客户端解耦 本指南基于 Webiny 仓库CMS后端前端PuerTS Unity 生成控制Filter完全指南过滤编译错误接口、JS 权限控制与 xIl2cpp 类型过滤PuerTS Unity 生成控制Filter完全指南过滤编译错误接口、JS 权限控制与 xIl2cpp 类型过滤 本篇技术指南围绕 PuerTS 的 FCMS后端前端Webiny-js 从 Elasticsearch 迁移到 OpenSearchwebiny/api-opensearch 包实现完全指南Webiny js 从 Elasticsearch 迁移到 OpenSearch webiny/api opensearch 包实现完全指南 导读 本文围绕CMS后端前端上一篇终极LSPosed框架完全指南30分钟学会Android应用自定义开发下一篇LaZagne云服务凭证提取Office 365与Google Workspace密码恢复创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网