ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Backstage 后端系统构建指南:从最小后端到多后端拆分与启动容错配置

Backstage 后端系统构建指南:从最小后端到多后端拆分与启动容错配置 Backstage 后端系统构建指南从最小后端到多后端拆分与启动容错配置【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文以 Backstage 官方文档《Building Backends》为核心系统讲解如何使用新后端系统New Backend System创建、装配与定制自己的 Backstage 后端包括createBackend的轻量初始化、插件/模块/服务的装配方式、通过配置与服务工厂完成定制、将单体后端拆分为多套独立部署以及用backend.startup配置控制插件启动失败时的容错行为。读完本文你将掌握一套可复制、可运行的后端搭建与运维方案并能结合仓库源码理解其底层实现。整体概览一个最小后端由什么构成Backstage 的后端本质上是一个轻量级的 Node.js 包只需要一个带package.json的目录和一个src/index.ts入口文件不考虑周围的工具链与文档的话仅此而已。这个包通常放在 Backstage monorepo 的packages/backend目录下但位置完全由你决定。任何通过backstage/create-app创建的项目都会自带这样一个后端包因此绝大多数情况下你不需要从零手写它。当你用backstage/create-app创建新项目时得到的后端src/index.ts大致长这样import { createBackend } from backstage/backend-defaults; // 以下示例中省略此行 const backend createBackend(); backend.add(import(backstage/plugin-app-backend)); backend.add(import(backstage/plugin-catalog-backend)); backend.add(import(backstage/plugin-scaffolder-backend)); backend.add( import(backstage/plugin-catalog-backend-module-scaffolder-entity-model), ); backend.start();初始模板里还会有更多插件和模块但整体结构是一致的。这段代码做的事情可以拆成三步调用createBackend创建后端实例。它负责把喂给后端的各种特性features装配在一起并为插件提供所有核心服务的默认实现。通过backend.add(...)安装特性。特性分三类**插件plugins**是独立的功能单元**模块modules**用于增强某个已有插件或模块**服务services**则用于更深层的定制可以覆盖默认行为。需要注意每个模块只能瞄准一个插件而且该插件必须存在于同一个后端中。调用backend.start()启动后端。此时后端才开始真正做初始化工作——创建实例本身不会执行任何实际操作所有工作都被推迟到start()调用时进行。关于createBackend的底层实现可以到 packages/backend-defaults/src/CreateBackend.ts 中查看它内部调用createSpecializedBackend并传入一长串defaultServiceFactoriesauditor、auth、cache、database、discovery、logger、scheduler 等约 27 个服务工厂。也就是说createBackend是开箱即用batteries included的高层入口而createSpecializedBackend是更底层、不预装任何服务的原始入口。更详细的说明可参考后端实例架构文档。如果你已经有一个尚未迁移到新后端系统的旧后端可参考迁移指南其中讲解了如何把旧式的makeCreateEnvplugins/*.ts结构逐步收敛为上述极简形态以及如何用legacyPlugin桥接旧式插件。仓库实例一个真实后端的装配清单理论之外本仓库自带的示例后端 packages/backend/src/index.ts 就是一份极佳的装配清单参考。它在createBackend()之后通过backend.add(...)安装了 Auth、App、Catalog、Events、Kubernetes、Permissions、Proxy、Scaffolder、Search、TechDocs、Signals、Notifications、MCP Actions、User Settings 等大量后端插件及其模块。值得特别注意的是其中使用的一个进阶特性——特性加载器Feature Loaderimport { coreServices, createBackendFeatureLoader, } from backstage/backend-plugin-api; const searchLoader createBackendFeatureLoader({ deps: { config: coreServices.rootConfig, }, *loader({ config }) { yield import(backstage/plugin-search-backend); yield import(backstage/plugin-search-backend-module-catalog); yield import(backstage/plugin-search-backend-module-explore); yield import(backstage/plugin-search-backend-module-techdocs); if (config.has(search.elasticsearch)) { yield import(backstage/plugin-search-backend-module-elasticsearch); } }, }); backend.add(searchLoader);它用生成器函数按需批量加载一组特性并且可以在deps中声明对 root 作用域服务的依赖——这里就通过coreServices.rootConfig读取配置当检测到search.elasticsearch配置时才额外加载 Elasticsearch 搜索模块。这展示了配置驱动装配的思路后端可以依据静态配置动态决定要安装哪些功能。配套的 packages/backend/package.json 则给出了后端包的典型定义role: backend、main: dist/index.cjs.js、构建/启动脚本backstage-cli package build、backstage-cli package start以及大量backstage/plugin-*-backend与backstage/plugin-*-backend-module-*依赖。这说明一个后端包的依赖天然分为插件本体与插件模块两类二者都在index.ts中通过backend.add装配。定制方式一静态配置Configuration除安装现成插件与模块外后端定制有几种途径。最易上手的是静态配置详细写法可参考配置编写文档。后端本身的许多行为、以及大量插件/模块的行为都可以通过配置调整具体可配置项需要查阅各插件/模块自己的文档同时建议查阅核心服务文档其中也覆盖了核心服务的配置方法。典型配置文件如仓库根目录的 app-config.yaml后端在启动时会读取并下发到各插件。定制方式二覆盖核心服务Services服务是另一个重要的定制切入点它允许对后端做更深、更广的定制。服务与前端系统中的 Utility APIs 类似都是通过依赖注入把通用能力提供给插件和模块更深入的解释见服务架构文档。所有后端都必须安装一组核心服务负责日志、数据库访问、HTTP 服务等基础能力。好消息是当你使用backstage/backend-defaults提供的createBackend时这组服务已全部默认安装。这些服务全部可以被你自己的实现替换。最省事的替换方式是沿用现有实现、只加选项——许多核心服务支持这种用法并非全部因为有些服务没有有意义的选项。例如想要定制核心配置服务以启用远程配置加载可以这样写import { rootConfigServiceFactory } from backstage/backend-app-api; const backend createBackend(); backend.add( rootConfigServiceFactory({ remote: { reloadIntervalSeconds: 60 }, }), );这样配置目标中就可以传入 URL后端会每 60 秒轮询一次该 URL 获取配置变更。这里有一个例外框架内置的PluginMetadataService插件元数据服务由框架直接提供无法被覆盖。它是插件作用域服务向服务实例提供当前正在为哪个插件创建实例的信息是插件级定制的基石——例如默认日志服务正是通过它给每条日志打上插件 ID 标签。定制方式三完全自定义服务工厂Custom Service Implementations覆盖服务时你并不局限于现有实现完全可以提供自己的服务工厂service factory从而用完全自定义的实现全局覆盖某个服务或在现有实现基础上叠加额外逻辑。覆盖服务的写法与上文相同放进services选项但这次需要用createServiceFactory创建工厂。例如用自定义实现替换默认的LoggerServiceconst backend createBackend(); backend.add( createServiceFactory({ service: coreServices.logger, deps: { rootLogger: coreServices.rootLogger, plugin: coreServices.pluginMetadata, config: coreServices.rootConfig, }, factory({ rootLogger, plugin, config }) { const labels readCustomLogLabelsForPlugin(config, plugin); // 自定义逻辑 return rootLogger.child(labels); }, }), );这个例子背后涉及两个容易混淆的服务LoggerServicecoreServices.logger插件作用域服务负责为每个插件创建带插件专属上下文的日志实例默认实现会给日志附加一个包含插件 ID 的plugin标签。RootLoggerServicecoreServices.rootLoggerroot 作用域服务是真正的日志实现本体。上面的自定义实现就是从配置中读出额外的标签并叠加到rootLogger的子 logger 上。这恰好引出**服务作用域service scope**的概念服务只有两种作用域——plugin默认和root。插件作用域服务会为每个依赖它的插件各创建一个实例实现插件间的一定隔离root 作用域服务则在所有插件与服务间共享单个实例且无论是否有插件依赖它都会被初始化适合承载与具体插件无关的后端级关注点。root 作用域服务只能依赖其他 root 作用域服务而插件作用域服务可以依赖两者。从源码结构看这类成对服务rootLogger/logger、rootConfig/config等的划分正是为了既让 root 服务也能访问日志/配置又鼓励插件优先使用插件作用域版本。完整的服务接口、引用与工厂规范可阅读服务架构文档。拆分为多个后端Split Into Multiple Backends更高阶的部署形态是把后端插件拆分到多个后端部署中。拆分的好处独立扩缩容、性能与安全隔离等在部署扩展文档与威胁模型中有详细说明这里聚焦怎么拆。创建独立后端需要额外新建一个后端包它会与现有后端分别构建、分别部署。目前yarn new还没有提供后端模板最快的做法是复制现有后端包再修改。命名取决于你的拆分方式这里以简单后缀为例目录结构可能变成packages/ backend-a/ src/ index.ts package.json - name: backend-a backend-b/ src/ index.ts package.json - name: backend-b然后裁剪各自的src/index.ts只保留想归属该后端的插件与模块。例如要把 Scaffolder 插件拆出去backend-a可能是const backend createBackend(); backend.add(import(backstage/plugin-app-backend)); backend.add(import(backstage/plugin-catalog-backend)); backend.add( import(backstage/plugin-catalog-backend-module-scaffolder-entity-model), ); backend.start();而backend-b则是const backend createBackend(); backend.add(import(backstage/plugin-scaffolder-backend)); backend.start();注意backend-b的package.json也要同步清理依赖移除不再需要的插件包。把后端拆成两套独立部署后剩下的关键问题是让它们能互相通信——这也是最繁琐的部分因为 Backstage 目前没有现成的开箱即用方案。你需要为两个后端手动配置自定义的DiscoveryService实现让它们能返回彼此正确的 URL在前端提供自定义的DiscoveryApi实现除非你通过一个负责路由的反向代理把两个后端统一暴露出来。多后端部署架构示例下面是一个更复杂的示例三套后端部署每套承载各自的插件与模块前端与各后端实例之间有一个反向代理负责把流量路由到正确的实例。作为加固选项该代理也可以配置为带认证的反向代理拒绝未认证用户访问后端实例。在这个示例中Catalog 与 Search 插件被拆到一套后端部署代理把/api/catalog/和/api/search/的流量全部路由到该实例。通过这种分离这两个插件可以独立扩缩容与部署并且在性能与安全上相互隔离同理TechDocs 与 Scaffolder 也被单独拆出其余流量则路由到承载 App、Auth、Proxy 插件的实例。图中还可以看到每个插件拥有自己逻辑上的数据库但通常共享同一个数据库管理系统DBMS实例——这当然不是硬性要求你可以按需进一步拆分或合并数据库。启动配置控制插件启动失败时的行为backend.startup配置块用于控制后端在插件或插件模块启动失败时的行为。默认情况下任何插件或模块的启动失败都是致命错误会导致后端中止启动。该配置允许你把特定插件/模块设为可选或翻转全局默认值让所有插件/模块默认可选、只有显式要求的才必须成功。插件启动失败处理默认情况下插件启动失败会让后端中止。你可以按插件用onPluginBootFailure: continue改变这一行为backend: startup: plugins: catalog: onPluginBootFailure: continue配置后如果catalog插件在启动时崩溃后端会记录错误并继续启动其余插件。这在排查与数据相关的问题时非常有用——可以让一个会崩溃的插件保持安装状态同时让后端其余部分继续对外服务。插件模块启动失败处理对单个插件模块可以用onPluginModuleBootFailure做同样的控制backend: startup: plugins: catalog: modules: github: # moduleId即 createBackendModule({ moduleId: ... }) 中声明的 ID onPluginModuleBootFailure: continue这允许github目录模块失败而不拖垮catalog插件或后端其余部分。注意modules下的键是createBackendModule中声明的moduleId而不是插件名或实体提供者entity provider名。设置全局默认值与其逐个把插件设为continue不如翻转全局默认值让所有插件失败时都继续只要求特定插件必须成功backend: startup: default: onPluginBootFailure: continue plugins: auth: onPluginBootFailure: abort这个示例中除auth被显式设为abort必须成功启动外其余插件全部可选。default机制对模块同样生效通过onPluginModuleBootFailurebackend: startup: default: onPluginModuleBootFailure: continue plugins: catalog: modules: github: onPluginModuleBootFailure: abort完整配置参考backend: startup: # 未按插件/模块单独指定时应用的全局默认值 default: # 默认值为 abort。设为 continue 可使所有插件默认可选。 onPluginBootFailure: abort # 或 continue # 默认值为 abort。设为 continue 可使所有插件模块默认可选。 onPluginModuleBootFailure: abort # 或 continue # 按插件、按模块的覆盖配置 plugins: pluginId: # 覆盖该插件默认的启动失败行为。 onPluginBootFailure: abort # 或 continue modules: moduleId: # 覆盖该插件模块默认的启动失败行为。 onPluginModuleBootFailure: abort # 或 continue这一配置的底层解析逻辑可以在 packages/backend-app-api/src/wiring/createAllowBootFailurePredicate.ts 中看到它启动时一次性读取backend.startup.default.onPluginBootFailure、backend.startup.default.onPluginModuleBootFailure的默认值再读取backend.startup.plugins下按插件、按模块的覆盖项最后返回一个(pluginId, moduleId?) boolean的谓词函数——返回true表示允许该插件/模块启动失败即配置为continue否则按abort处理。这也印证了配置键的取值仅支持abort与continue两种且模块级配置一定嵌套在对应插件之下。值得一提的是Backend.start()返回一个BackendStartupResult包含所有插件与模块详细的成功/失败状态和耗时信息启动失败时会抛出BackendStartupError其中携带完整的启动结果便于诊断究竟是哪个插件或模块失败。这对构建额外的监控或调试工具很有价值详见后端实例架构文档。小结与延伸阅读搭建一个 Backstage 后端可以概括为三步createBackend()创建实例 →backend.add(...)装配插件/模块/服务工厂 →backend.start()启动。在此基础上你可以通过静态配置、给现有服务工厂传选项、或编写完全自定义的服务工厂三种粒度进行定制当单体后端不再满足部署需求时可以按插件拆分出多套独立后端并用自定义DiscoveryService/反向代理打通互访最后用backend.startup配置为启动阶段引入容错能力。继续深入可以参考仓库内以下资料后端实例Backend架构文档createBackend/createSpecializedBackend的关系、BackendStartupResult与BackendStartupError服务Services架构文档服务引用、服务工厂、作用域、Multiton、默认工厂等完整机制核心服务索引Auth、Cache、Database、Discovery、Logger、Scheduler 等全部核心服务的文档入口后端迁移指南旧后端到新后端系统的完整迁移步骤示例后端装配清单 与 示例后端包定义真实项目的组装与依赖结构createBackend 实现defaultServiceFactories完整列表启动失败谓词实现backend.startup配置的解析与判定逻辑【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表