资讯详情

资讯详情

Backstage 后端插件迁移指南:将旧版 Router 插件迁移到新后端系统(New Backend System)

Backstage 后端插件迁移指南将旧版 Router 插件迁移到新后端系统New Backend System【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage本文是一份面向 Backstage 插件作者的实操迁移指南围绕新后端系统New Backend System下如何将传统导出 express.Router 并在后端 index.ts 中接线的旧版后端插件一步步改造成基于createBackendPlugin的新式插件。文章以 kubernetes 后端插件为贯穿案例覆盖依赖注入coreServices、扩展点Extension Points设计、本地开发服务器搭建以及旧系统支持的移除流程读完你可以独立完成绝大多数现有后端插件的迁移并掌握为插件设计可扩展接口的完整方法论。迁移的本质从接线 Router到注册插件在旧版legacy后端系统中绝大多数后端插件的形态高度一致导出createRouter(options)工厂函数返回一个express.Router再由后端应用的index.ts手动把 Router 挂载到 Express 应用上。这类插件的依赖logger、config、catalogApi、discovery 等全部通过RouterOptions参数对象显式传入。新后端系统改变了这一切插件不再自行接线而是通过createBackendPlugin声明自己的依赖由后端框架在启动时统一注入并通过coreServices.httpRouter服务注册 HTTP 路由。迁移的核心工作只有两件事确保插件运行所需的依赖都可以从coreServices或其它插件的 node 库扩展点拿到把原来返回的 Router 注册到 HTTP Router 服务上。以 kubernetes 后端插件为例旧版代码结构如下来源见原文档示例// backstage/plugin-kubernetes-backend/src/service/router.ts import { KubernetesBuilder } from ./KubernetesBuilder; export interface RouterOptions { logger: Logger; config: Config; catalogApi: CatalogApi; clusterSupplier?: KubernetesClustersSupplier; discovery: PluginEndpointDiscovery; } export async function createRouter( options: RouterOptions, ): Promiseexpress.Router { const { router } await KubernetesBuilder.createBuilder(options) .setClusterSupplier(options.clusterSupplier) .build(); return router; }注意这里的关键点KubernetesBuilder内部已经完成了 Router 的构建旧代码只是负责把依赖喂进去、把 Router 吐出来。因此迁移时完全可以复用KubernetesBuilder只需把它包进新后端系统的插件定义中。第一步把 RouterOptions 依赖映射到核心服务新后端系统中RouterOptions里的每一项依赖几乎都能找到对应的核心服务core service旧版RouterOptions字段新后端系统中的对应项说明loggercoreServices.logger日志服务configcoreServices.rootConfig根配置服务catalogApicatalogServiceRefCatalog 插件提供的服务来自backstage/plugin-catalog-nodediscoverycoreServices.discovery插件端点发现服务clusterSupplier自定义见下文扩展点插件级定制项无法由 coreServices 直接提供catalogServiceRef并不在coreServices中而是由 catalog 插件通过其 node 库backstage/plugin-catalog-node导出的服务引用。这正是新后端系统依赖注入的两种来源核心服务coreServices与其他插件提供的服务引用service ref。第二步用 createBackendPlugin 包装并注册 Router将上述依赖映射到createBackendPlugin的register回调中通过registerInit注册初始化逻辑最后用http.use(router)把 Router 挂到 HTTP Router 服务上import { coreServices, createBackendPlugin, } from backstage/backend-plugin-api; import { catalogServiceRef } from backstage/plugin-catalog-node; import { Router } from express; import { KubernetesBuilder } from ./KubernetesBuilder; export const kubernetesPlugin createBackendPlugin({ pluginId: kubernetes, register(env) { env.registerInit({ deps: { logger: coreServices.logger, config: coreServices.rootConfig, catalogApi: catalogServiceRef, discovery: coreServices.discovery, // The http router service is used to register the router created by the KubernetesBuilder. http: coreServices.httpRouter, }, async init({ config, logger, catalogApi, discovery, http }) { const { router } await KubernetesBuilder.createBuilder({ config, logger, catalogApi, discovery, }).build(); // We register the router with the http service. http.use(router); }, }); }, });几点需要特别注意pluginId必须与后端注册时使用的路径约定一致。例如pluginId: kubernetes意味着该插件挂载在/api/kubernetes路径下http.use(router)是迁移的标志性动作它把插件构建出的 express Router 交给 HTTP Router 服务统一托管deps中的键名与init回调解构的参数名一一对应可以随意命名但强烈建议与语义保持一致。第三步默认导出插件实例并注册到后端最后在包的src/index.ts中把插件实例作为默认导出export { kubernetesPlugin as default } from ./plugin.ts;这样一来用户就可以通过一行代码把插件注册进后端// packages/backend/src/index.ts backend.add(import(backstage/plugin-kubernetes-backend));backend.add接受动态 import框架会解析包的默认导出并完成插件实例化、依赖注入与初始化。至此一个最简迁移已经完成。处理插件的自定义选项静态配置优先扩展点兜底细心的读者会发现上面的迁移丢掉了原插件的一个可选参数clusterSupplier。它是插件暴露给集成者的定制点处理这类插件级选项正是新后端系统设计的关键考量通常有两种方案。方案一静态配置优先选择如果定制逻辑可以完全由配置表达优先使用静态配置。例如可以设计一组内置实现供选择或让配置完全决定ClusterSupplier的行为。使用静态配置做定制永远是可行时的首选因为它无需任何额外代码即可完成集成/* omitted imports but they remain the same as above */ const kubernetesPlugin createBackendPlugin({ pluginId: kubernetes, register(env) { env.registerInit({ deps: { /* omitted dependencies but they remain the same as above */ }, async init({ config, logger, catalogApi, discovery, http }) { // Note that in a real implementation this would be done by the KubernetesBuilder instead, // but here weve extracted it into a separate call to highlight the example. const configuredClusterSupplier readClusterSupplierFromConfig(config); const { router } await KubernetesBuilder.createBuilder({ config, logger, catalogApi, discovery, }) .setClusterSupplier(configuredClusterSupplier) .build(); http.use(router); }, }); }, });方案二扩展点Extension Points但很多定制无法用静态配置表达——比如本例中集成者需要以代码实现任意的KubernetesClustersSupplier接口。这正是新后端系统扩展点机制的用武之地。扩展点的完整设计与模块机制可参阅 扩展点文档 与 模块文档。扩展点的核心思想是插件在register阶段把自己提供的接口实现注册到后端同插件的模块Module通过声明对扩展点的依赖来注入额外功能。其工作流如下1. 创建 node 库包并定义扩展点首先创建一个backstage/plugin-kubernetes-node包。单独的 node 包可以避免模块对插件包本身的直接依赖防止插件包被重复安装这也是 Backstage 插件生态的约定——插件扩展点一律从 node 库包导出。在包中定义扩展点import { createExtensionPoint } from backstage/backend-plugin-api; export interface KubernetesClusterSupplierExtensionPoint { setClusterSupplier(supplier: KubernetesClustersSupplier): void; } /** * An extension point that allows other plugins to set the cluster supplier. */ export const kubernetesClustersSupplierExtensionPoint createExtensionPointKubernetesClusterSupplierExtensionPoint({ id: kubernetes.cluster-supplier, });createExtensionPoint需要提供接口类型与全局唯一的id。关于扩展点接口设计的更多细节例如接口应只支持添加而不支持移除、单例 setter 模式等参见 扩展点设计。2. 在插件中注册扩展点实现接着让 kubernetes 后端插件自己支持该扩展点。插件的register回调里先用registerExtensionPoint注册一个实现供后续模块调用同时把接收到的 supplier 保存在闭包中供init阶段使用/* omitted other imports but they remain the same as above */ import { kubernetesClustersSupplierExtensionPoint } from backstage/plugin-kubernetes-node; export const kubernetesPlugin createBackendPlugin({ pluginId: kubernetes, register(env) { let clusterSupplier: KubernetesClustersSupplier | undefined undefined; // We register the extension point with the backend, which allows modules to // register their own ClusterSupplier. env.registerExtensionPoint(kubernetesClustersSupplierExtensionPoint, { setClusterSupplier(supplier) { if (clusterSupplier) { throw new Error(ClusterSupplier may only be set once); } clusterSupplier supplier; }, }); env.registerInit({ deps: { /* omitted dependencies but they remain the same as above */ }, async init({ config, logger, catalogApi, discovery, http }) { const { router } await KubernetesBuilder.createBuilder({ config, logger, catalogApi, discovery, }) .setClusterSupplier(clusterSupplier) .build(); http.use(router); }, }); }, });这里setClusterSupplier采用单例 setter模式只允许设置一次重复设置直接抛错避免多个模块互相覆盖。这种设计意图是集成者只能安装一个 supplier一旦装错卸载对应模块即可插件自身无需提供移除逻辑详见扩展点设计文档中对只增不减接口的讨论。3. 用模块注入自定义实现外部团队现在可以编写模块把任意ClusterSupplier实现注入 kubernetes 插件。模块通过createBackendModule创建并在deps中声明对扩展点的依赖import { kubernetesClustersSupplierExtensionPoint } from backstage/plugin-kubernetes-node; // This is a custom implementation of the ClusterSupplier interface. import { GoogleContainerEngineSupplier } from ./GoogleContainerEngineSupplier; export default createBackendModule({ pluginId: kubernetes, moduleId: gke-supplier, register(env) { env.registerInit({ deps: { supplier: kubernetesClustersSupplierExtensionPoint, }, async init({ supplier }) { supplier.setClusterSupplier(new GoogleContainerEngineSupplier()); }, }); }, });注意模块的pluginId必须与目标插件一致kubernetes这样框架才会把该模块与 kubernetes 插件关联并保证所有模块先初始化、插件后初始化的执行顺序——即插件init执行时所有模块注入的扩展一定已经就绪。4. 安装模块集成者只需把模块包与插件包一起加入后端backend.add(import(backstage/plugin-kubernetes-backend)); backend.add(import(internal/gke-cluster-supplier));仓库源码印证kubernetes 插件实际有 6 个扩展点上述文档示例并非虚构。在当前仓库中kubernetes 插件已经按新后端系统完成了迁移其真实实现位于 plugins/kubernetes-backend/src/plugin.ts可以看到它注册了多达 6 个扩展点kubernetesObjectsProviderExtensionPointid:kubernetes.objects-providerkubernetesClusterSupplierExtensionPointid:kubernetes.cluster-supplierkubernetesAuthStrategyExtensionPointid:kubernetes.auth-strategykubernetesFetcherExtensionPointid:kubernetes.fetcherkubernetesServiceLocatorExtensionPointid:kubernetes.service-locatorkubernetesRouterExtensionPointid:kubernetes.router这些扩展点的定义都集中在 plugins/kubernetes-node/src/extensions.ts。从源码结构看实际实现与文档示例存在一处演化接口方法名是addClusterSupplier而非示例中的setClusterSupplier并且支持传入对象实例或工厂函数KubernetesClusterSupplierFactory两种形式见该文件中的类型定义export type KubernetesClusterSupplierFactory (opts: { getDefault: () PromiseKubernetesClustersSupplier; }) PromiseKubernetesClustersSupplier; export interface KubernetesClusterSupplierExtensionPoint { addClusterSupplier( clusterSupplier: | KubernetesClustersSupplier | KubernetesClusterSupplierFactory, ): void; }工厂形式factory变体的存在意味着插件可以调用getDefault()拿到默认实现再基于它包装出定制实现同时把启动失败的归因能力reportModuleStartupFailure保留给模块。这种默认实现 工厂包装的模式在真实代码中体现得淋漓尽致——在plugin.ts的init中KubernetesInitializer.create({...})接收extPointFetcher.getFetcher()、extPointClusterSuplier.getClusterSupplier()等扩展点当前持有的值未设置时为undefined从而回退到默认行为。plugin.ts中registerInit的deps也展示了新后端系统依赖注入的完整面貌——除文档示例中的httpRouter、logger、rootConfig、discovery与catalogServiceRef外还声明了permissions、auth、httpAuth、auditor等核心服务。这印证了绝大多数依赖都来自 coreServices 或其它插件的 node 库服务引用这一迁移前提。此外当前 plugins/kubernetes-backend/src/index.ts 仍然以默认导出方式导出kubernetesPlugin这正对应文档要求的默认导出约定该文件同时仍导出./auth、./service、./types属于迁移后的历史遗留导出新代码应遵循下文移除旧系统支持的清理流程。本地开发服务器让迁移后的插件可独立运行调试插件迁移完成后你可能希望像旧系统那样在本地单独运行插件进行调试。旧方式依赖src/run.ts与src/service/standaloneServer.tsbackstage-cli 为旧插件准备的本地启动文件新系统下这些文件不再需要可以删除。取而代之的是在dev/index.ts中创建一个轻量开发后端。当前仓库中 kubernetes 插件的开发后端实现在 plugins/kubernetes-backend/dev/index.ts与文档示例完全一致// This package should be installed as a dev dependency import { createBackend } from backstage/backend-defaults; const backend createBackend(); // Path to the file where the plugin is export as default backend.add(import(../src)); backend.start();这个开发服务器会自动装配默认的依赖工厂。如果插件依赖的某些服务需要 mock例如rootConfig配置服务可以使用backstage/backend-test-utils提供的mockServices工厂覆盖//... // This package should be installed as devDependencies import { mockServices } from backstage/backend-test-utils; const backend createBackend(); // ... backend.add( mockServices.rootConfig.factory({ data: { // your config mocked values goes here }, }), ); // ...如需为其它服务编写自定义 mock 工厂可参考 自定义服务实现 与 核心服务配置 两篇文档。最后在插件根目录运行yarn start即可启动本地开发服务器。移除旧系统支持三步清理流程当新后端插件形态稳定后应逐步移除旧系统支持让 API 面保持干净。官方推荐的节奏是一个版本先标记废弃deprecate下一个版本再删除。1. 废弃默认导出之外的公开导出把createRouter、RouterOptions等旧导出加上deprecated注解给使用者明确的迁移信号// backstage/plugin-kubernetes-backend/src/service/router.ts import { KubernetesBuilder } from ./KubernetesBuilder; /** * public * deprecated Please migrate to the new backend system. */ export interface RouterOptions { logger: Logger; config: Config; catalogApi: CatalogApi; clusterSupplier?: KubernetesClustersSupplier; discovery: PluginEndpointDiscovery; } /** * public * deprecated Please migrate to the new backend system. */ export async function createRouter( options: RouterOptions, ): Promiseexpress.Router { const { router } await KubernetesBuilder.createBuilder(options) .setClusterSupplier(options.clusterSupplier) .build(); return router; }注意插件内部仍然可以使用createRouter及其相关导入但不应再作为公开 API 导出。一旦createRouter导出被删除迁移后的插件内部代码必须同步重构移除对已废弃导入的引用。同时建议迁移后的插件尽量避免使用backstage/backend-common和backstage/backend-tasks——它们会随旧系统支持终止一并被删除大多数废弃导入处都附有替代用法的说明。如果你的包包含api-report.md废弃修改后需要运行yarn build:api-reports重新生成 API 报告。建议检查报告中除新后端插件外的其它导出——新系统中插件通过扩展点扩展而非传参定制因此任何 Builder 或辅助方法都应迁入该插件专属的库包如plugin-kubernetes-backend-node详见 package roles 说明。完成废弃清理后index.ts应只剩默认导出// backstage/plugin-kubernetes-backend/src/index.ts export { kubernetesPlugin as default } from ./plugin;2. 废弃/alpha子路径如果存在如果你之前通过alpha导出提前提供了新后端插件需要废弃这些 alpha 导出并改由根路径index.ts统一导出// backstage/plugin-name-backend/src/alpha.ts /** * alpha * deprecated Please import from the root path instead. */ export default createPlugin({ //... });3. 删除旧文件旧版本地启动文件src/run.ts、src/service/standaloneServer.ts在新系统下已无用直接删除开发后端统一收敛到dev/index.ts。小结迁移到新后端系统的收益是结构性的依赖注入由框架托管、插件生命周期清晰、通过扩展点实现插件 模块的可组合生态。整个迁移路径可以概括为把RouterOptions中的依赖逐一映射到coreServices或其它插件的 node 库服务引用用createBackendPlugin包裹原有构建逻辑通过http.use(router)注册路由将插件实例作为包默认导出用户侧一行backend.add(import(...))完成注册插件级定制项优先做静态配置复杂定制则设计 node 库扩展点 模块机制用dev/index.ts轻量后端 mockServices做本地调试分版本逐步废弃并移除旧系统导出最终index.ts只剩默认导出。只要插件形态是构建一个 Router这套流程几乎可以无差别套用——仓库中 kubernetes 插件的新后端实现 就是一个已经完成迁移、且扩展点设计远超市面示例的现成范本。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →