ARTICLE DETAIL

资讯详情

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

Backstage 后端功能加载器(Backend Feature Loaders)深度指南:条件安装、动态装载与服务工厂覆盖

Backstage 后端功能加载器(Backend Feature Loaders)深度指南:条件安装、动态装载与服务工厂覆盖 Backstage 后端功能加载器Backend Feature Loaders深度指南条件安装、动态装载与服务工厂覆盖【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstageBackstage 的新后端系统New Backend System提供了一种名为Backend Feature Loader后端功能加载器的机制用于以编程方式按需选择并安装后端功能例如根据静态配置启用或禁用搜索插件、在运行时动态加载功能、或依据系统状态条件式装配服务。本文以 feature loaders 架构文档 为主体结合backstage/backend-plugin-api与backstage/backend-app-api的源码实现与测试用例完整讲解createBackendFeatureLoader的 API 形态、四种典型用法、优先级规则与底层工作原理读完即可在自有 Backstage 后端中编写、注册和调试功能加载器。什么是 Backend Feature Loader在 Backstage 新后端系统的四大构建块Backend、Plugin、Service、Extension Point/Module参见架构总览之外功能加载器是一种“批量装配工具”。它的定位与插件、模块不同插件Plugin提供实际功能模块Module通过 Extension Point 扩展其他插件功能加载器Feature Loader本身不实现任何业务而是在启动阶段决定把哪些插件、模块、服务工厂甚至其他功能加载器交给backend.add(...)安装。从使用场景看它适合三类典型需求基于静态配置启用/禁用功能——例如用app-config.yaml里的一个开关决定是否安装整组搜索插件运行时动态加载功能——例如启动时从磁盘或远端元数据服务拉取信息决定要安装哪些特性依据系统状态条件加载——例如在生成器generator中按分支逐步yield出特性列表。核心 APIcreateBackendFeatureLoader功能加载器通过backstage/backend-plugin-api导出的createBackendFeatureLoader函数定义其完整签名如下源码见 createBackendFeatureLoader.tsexport interface CreateBackendFeatureLoaderOptions TDeps extends { [name in string]: unknown }, { deps?: { [name in keyof TDeps]: ServiceRefTDeps[name], root; }; loader( deps: TDeps, ): | IterableBackendFeature | Promise{ default: BackendFeature } | PromiseIterableBackendFeature | Promise{ default: BackendFeature } | AsyncIterableBackendFeature | { default: BackendFeature }; }关键点有三个deps只能声明 root 作用域的服务。这是与插件、模块的最大区别功能加载器只能依赖如 root config、root logger 这类根级服务不能依赖coreServices.logger、coreServices.database等插件作用域服务。原因是加载器运行在插件实例化之前此时插件级服务尚不存在。loader返回的是一组BackendFeature。BackendFeature是“可以传给backend.add(...)的对象”的总称包括服务工厂、插件、模块、甚至另一个功能加载器其类型定义见 types.ts。loader支持四种形态同步函数、异步函数async、生成器函数*、异步生成器函数async *返回格式可以是数组、Iterable或AsyncIterable条目既可以是BackendFeature本身也可以是Promise{ default: BackendFeature }即import(...)动态导入的结果。底层实现结果如何被摊平在 createBackendFeatureLoader.ts 中内部loader会把用户定义的loader返回值逐条“摊平”async loader(deps: TDeps) { const it await options.loader(deps); const result new ArrayBackendFeature(); for await (const item of it) { if ($$type in item item.$$type backstage/BackendFeature) { result.push(item); } else if (default in item) { result.push(item.default); } else { throw new Error(Invalid item ${item}); } } return result; }这段代码解释了为什么文档示例里可以混用import(backstage/plugin-search-backend)动态导入结果是{ default: BackendFeature }和createBackendPlugin({...})本身就是BackendFeature二者都会被识别并统一收纳。如果某个条目既不是BackendFeature也没有default字段会直接抛出Invalid item错误保证装配过程的严格性。四种典型用法以下示例全部继承自 feature loaders 文档可直接复制到自己的后端中运行。1. 简单特性列表批量安装一组功能最简单的用法是让loader直接返回一组要安装的特性。例如一次性启用搜索功能所需的全部插件export default createBackendFeatureLoader({ loader() { return [ import(backstage/plugin-search-backend), import(backstage/plugin-search-backend-module-catalog), import(backstage/plugin-search-backend-module-explore), import(backstage/plugin-search-backend-module-techdocs), ]; }, });也可以封装一组自研特性插件、模块混合且支持异步loaderexport default createBackendFeatureLoader({ // Async loader is fine too async loader() { return [ createBackendPlugin({ // ... }), createBackendModule({ // ... }), ] }, });注意这里的import(...)返回的是 Promise而loader的返回类型恰好支持Promise{ default: BackendFeature }条目测试用例 createBackendFeatureLoader.test.ts 明确验证了“动态导入格式”与“直接传入BackendFeature”可以混合出现在同一个数组中。2. 基于配置的条件加载功能加载器可以访问 root 作用域服务如配置服务从而根据app-config.yaml的内容决定是否安装功能。文档推荐用生成器函数*loader来实现分支式的条件逻辑export default createBackendFeatureLoader({ deps: { config: coreServices.rootConfig, }, // The * in front of the function name makes it a generator function *loader({ config }) { // Example of a custom config flag to enable search if (config.getOptionalString(customFeatureToggle.search)) { 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); } }, });这里的coreServices.rootConfig即 Root Config Service它从app-configYAML 文件读取配置因此你可以在app-config.yaml中声明customFeatureToggle: search: true当开关为真时才yield出搜索相关的四个插件为关闭时则完全不安装。这种“配置驱动装配”让同一份后端代码在不同环境开发/生产/多租户下可以呈现出不同的功能集合而无需改动代码。3. 覆盖服务工厂优先级低于 backend.add功能加载器常常被用来“批量注册服务工厂”。这里有一条非常重要的优先级规则由功能加载器注册的服务工厂优先级低于直接通过backend.add添加的。因此可以用加载器提供一大批默认服务实现同时又允许在个别服务上单独覆盖const backend createBackend(); backend.add( createBackendFeatureLoader({ async *loader() { yield import(./commonDiscoveryService); // discovery service yield import(./commonRootLoggerService); // root logger service }, }), ); backend.add(import(./myDiscoveryService)); // discovery service结果后端启动时使用./myDiscoveryService作为 discovery 服务的实现./commonDiscoveryService被忽略而./commonRootLoggerService因为没有直接覆盖仍会被使用。此外文档明确了两条边界添加顺序无关功能加载器与服务工厂之间的先后顺序不影响最终生效结果加载器之间无优先级如果两个不同的功能加载器为同一个服务都添加了工厂后端将启动失败冲突检测发生在backend.start()的启动校验阶段参见 后端实例文档 中关于“启动时校验所有特性、确保没有冲突”的说明。4. 动态逻辑从外部数据源决定装载loader可以是异步的因此可以在启动时读取本地磁盘、请求远端服务再根据结果决定装载哪些功能。文档给出的是异步生成器async *的例子export default createBackendFeatureLoader({ // The async * in front of the function name makes it an async generator function. async *loader() { const localMetadata await readMetadataFromDisk(); if (localMetadata.enableSearch) { yield import(backstage/plugin-search-backend); yield import(backstage/plugin-search-backend-module-catalog); const remoteMetadata await fetchMetadata(); if (remoteMetadata.enableExplore) { yield import(backstage/plugin-search-backend-module-explore); } if (remoteMetadata.enableTechDocs) { yield import(backstage/plugin-search-backend-module-techdocs); } } }, });注意动态逻辑中 await 的对象readMetadataFromDisk()、fetchMetadata()在真实项目中需要自行实现例如读取本地 JSON、调用内部配置中心 API其返回结果用于驱动后续的yield。这种“先查后装”的模式适用于需要与环境事实对齐的装配场景例如按集群、租户或部署区域差异化装载插件。源码级验证实现与测试证据四种 loader 形态均有测试覆盖createBackendFeatureLoader.test.ts 用一个feature和dynamicFeature Promise.resolve({ default: feature })交叉验证了全部 8 种组合同步/异步 × 数组/生成器 × 直接特性/动态导入全部resolves.toEqual([feature])。这意味着无论你采用文档中的哪种写法最终装配结果等价。只允许 root 作用域依赖编译期强制测试中专门有一组用例验证“仅允许 root 作用域服务”这一约束createBackendFeatureLoader({ deps: { rootLogger: coreServices.rootLogger, }, loader: () [], }); createBackendFeatureLoader({ deps: { // ts-expect-error logger: coreServices.logger, }, loader: () [], });依赖coreServices.logger插件作用域会触发 TypeScript 编译错误而coreServices.rootLoggerroot 作用域可以正常通过。这从类型层面保证了加载器只能在插件实例化之前访问根级基础设施。启动时的类型校验当backend.add(loader)后后端启动阶段会在 validateBackendFeature.ts 中对特性做统一校验featureType loader的特性会被识别为type: loader并登记为feature loader description描述形如created at 调用点便于错误信息定位加载器定义位置。注册方式与运行前提功能加载器与插件、模块一样通过backend.add(...)注册最终由backend.start()统一启动装配关于createBackend、add、start的完整流程见后端实例文档。常见做法是把加载器写成独立的默认导出模块再在packages/backend/src/index.ts中引入import { createBackend } from backstage/backend-defaults; import featureLoader from ./featureLoader; // 自定义加载器 const backend createBackend(); // 加载器可以和其他特性混合添加 backend.add(featureLoader); backend.add(import(backstage/plugin-catalog-backend)); backend.start();适用前提当前仓库的新后端系统要求使用backstage/backend-plugin-api与backstage/backend-app-api旧后端系统的createPlugin/registerRouter体系下没有功能加载器这一概念。注意事项与最佳实践不要用加载器替代插件/模块加载器只是“装配决策层”具体功能仍需由插件、模块和服务工厂实现。善用生成器表达分支条件逻辑较多时生成器*/async *比一次性构造数组更清晰且可以边 await 边 yield。牢记服务冲突规则两个加载器同时注册同一服务工厂会导致启动失败需要覆盖时用backend.add直接添加其优先级高于加载器。根级服务是唯一的依赖入口需要读取配置就用coreServices.rootConfig需要打日志就用coreServices.rootLogger不要尝试注入插件作用域服务。动态逻辑要可观测async *loader中 await 外部数据源时建议配合 root logger 打印加载决策便于排查“为什么某功能没装上”。延伸阅读后端系统架构总览了解 Backend / Plugin / Service / Module 等构建块后端实例BackendcreateBackend、add、start与启动校验Root Config Service加载器条件判断常用的配置读取服务Root Logger Service加载器可依赖的日志服务及其配置参数createBackendFeatureLoader 源码核心实现createBackendFeatureLoader 测试四种形态与 root 依赖限制的验证用例【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表