Backstage 后端插件(Backend Plugin)架构全解:从createBackendPlugin到插件隔离规则
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
Backstage 的插件(Plugin)是后端系统的核心功能单元:Catalog、Scaffolder、Notifications 等所有业务能力都由插件提供。本文以 docs/backend-system/architecture/04-plugins.md 为主干,结合仓库内@backstage/backend-plugin-api的源码实现与真实插件(如 notifications-backend)的代码,系统讲解插件的定义方式、注册机制、安装流程、配置约定与生产级插件必须遵循的可扩展性与隔离性规则。读完本文,你将能够独立编写、安装、配置并正确架构一个 Backstage 后端插件。
上图展示了 Backstage 后端系统的几大构建模块:Backend(后端实例)、Plugins(插件)、Services(服务)、Extension Points(扩展点)与 Modules(模块),插件与插件之间只能通过网络通信。
插件在后端系统中的定位
在后端系统架构(01-index.md)中,插件是提供实际业务功能的模块。理解插件,需要先把握以下几点定位:
- 插件是功能的载体:默认的 Backstage 工程会把所有插件安装在同一个后端(backend)实例中,但也可以拆分为多个后端,每个后端承载一个或多个插件(02-backends.md)。
- 插件彼此完全独立:每个插件与其他插件不存在任何代码层面的直接通信,只能通过网络调用交互。因此每个插件都可以被看作一个独立的微服务(microservice)。
- 服务(Service)为插件提供公共能力:插件不需要从零实现日志、数据库、配置读取等基础设施,而是通过服务引用来获取实现。内置服务统一通过
coreServices暴露(详见 core services 总览)。
定义插件:createBackendPlugin
插件使用createBackendPlugin函数创建,通常从一个插件包(plugin package)中导出。createBackendPlugin位于@backstage/backend-plugin-api包,其类型定义与实现位于 packages/backend-plugin-api/src/wiring/createBackendPlugin.ts 与 packages/backend-plugin-api/src/wiring/types.ts。
一个最简插件的定义如下(取自原文档示例):
// plugins/example-backend/src/plugin.ts import { coreServices, createBackendPlugin, } from '@backstage/backend-plugin-api'; export const examplePlugin = createBackendPlugin({ pluginId: 'example', register(env) { env.registerInit({ deps: { logger: coreServices.logger, }, async init({ logger }) { logger.info('Hello from example plugin'); }, }); }, });每个插件都必须具备:
pluginId:插件的 ID,必须与包名中的插件 ID 一致(去掉-backend后缀)。ID 需符合 kebab-case 命名,仅允许字母、数字和短横线,且以字母开头(详见 08-naming-patterns.md)。register方法:接收一个env对象,用于声明插件的对外表面。
register回调中的三个关键注册点
从 types.ts 中的BackendPluginRegistrationPoints接口可以看到,env(reg)提供以下能力:
| 方法 | 用途 |
|---|---|
registerInit({ deps, init }) | 注册初始化函数,在后端启动时运行;deps声明服务依赖,init回调接收解析后的依赖实例 |
registerExtensionPoint(ref, impl) | 注册扩展点实现,供模块(Module)扩展插件能力(见 05-extension-points.md) |
registerConnection(registration)(内部) | 声明插件需要消费的连接类型 |
源码层面的校验逻辑
在createBackendPlugin的实现中(createBackendPlugin.ts),有几点值得注意:
- ID 校验:若
pluginId不匹配ID_PATTERN,会打印警告提示尽快修改;若连宽松的旧模式ID_PATTERN_OLD都不匹配,则直接抛出Invalid pluginId错误。 registerInit只能调用一次:重复调用会抛出registerInit must only be called once。- 必须在
register中调用registerInit:如果注册结束时没有初始化函数,会抛出registerInit was not called by register in <pluginId>。 registerExtensionPoint与registerInit的顺序约束:registerExtensionPoint必须在registerInit之前调用,否则报错。- 返回结构:
createBackendPlugin返回一个InternalBackendRegistrations类型的BackendFeature对象,其内部通过getRegistrations()惰性生成type: 'plugin-v1.1'的注册描述(包含pluginId、extensionPoints、connections与init),这使插件直到被真正加入后端时才完成注册描述的计算。
init中的依赖注入
registerInit的deps参数用于声明服务依赖,init回调则接收解析好的依赖对象。在上面的示例中,插件声明了对 logger 服务的依赖——这是所有 Backstage 后端插件可用的核心服务之一。完整的内置服务清单参见 core services 总览,包括 Auth、Cache、Database、Discovery、Http Router、Scheduler、Url Reader 等。插件自然也可以依赖其他库导出的服务(例如 notifications 插件依赖@backstage/plugin-signals-node的signalsServiceRef与@backstage/plugin-catalog-node的catalogServiceRef)。
将插件安装进后端实例
createBackendPlugin的返回值(如上例中的examplePlugin)是一个工厂函数,用于创建实际的插件实例。在后端实例中安装插件:
import { examplePlugin } from 'backstage-plugin-example-backend'; backend.add(examplePlugin);后端的创建与启动流程(02-backends.md)大致如下:
import { createBackend } from '@backstage/backend-defaults'; import scaffolderPlugin from '@backstage/plugin-scaffolder-backend'; // 创建后端实例 const backend = createBackend(); // 安装所需功能(插件、模块或服务工厂) backend.add(import('@backstage/plugin-catalog-backend')); // 也可以使用显式引用安装 backend.add(scaffolderPlugin); // 启动后端:初始化所有功能,并校验是否存在循环依赖等冲突 backend.start();注意backend.add()既可以接收同步的插件实例,也可以接收import('...')动态导入(返回 Promise)。后端在启动时会对所有功能做冲突校验(例如确保不存在循环依赖)。
默认导出约定:按包名引用插件
按约定,每个插件包都应把插件实例作为包的默认导出:
// plugins/example-backend/src/index.ts export { examplePlugin as default } from './plugin.ts';这样便可以直接通过包名引用安装插件,无需关心具名导出的名字:
backend.add(import('backstage-plugin-example-backend'));这一约定与包结构相互配合:plugin-<pluginId>-backend包承载后端插件实现,plugin-<pluginId>-node包承载扩展点与其他工具(详见 architecture-overview.md 的包架构说明)。
使用静态配置定制插件
为了让插件可定制,应优先使用静态配置(docs/conf/defining.md)。按约定,插件应把配置放在与插件 ID 一致的一级配置键下。例如,上面的示例插件可以这样配置:
example: message: Welcome to the example plugin在插件代码中,通过coreServices.rootConfig读取该配置(对应RootConfigService,详见 root-config 文档)。当静态配置不足以表达复杂的定制需求时,可以转而注册扩展点(Extension Point)来暴露更深层的定制能力,扩展点机制在 05-extension-points.md 中详细讲解。
插件规则(Rules of Plugins)
以下规则适用于 Backstage 插件生态中生产环境部署的插件。凡是维护在@backstage命名空间下的插件都应遵守,也推荐所有广泛分发的插件遵循。开发或测试环境是例外,可以为了简化开发流程而走捷径。
可扩展(Scalable)
插件必须始终面向水平扩展设计。这意味着:
- 不要在内存中保存任何状态;
- 如果必须保存状态,要确保状态在多个实例间的复制不会成为问题;
- 插件要么无状态(stateless),要么把状态存放在外部服务(如数据库)中。
这样当某个插件的负载增长时,可以横向增加该插件的实例数,而不必担心实例间状态不一致。
隔离(Isolated)
插件绝不能通过代码与其他插件直接通信,只能通过网络通信。若插件希望为其他插件或模块暴露外部接口,推荐通过 node-library 包(包角色说明见 docs/tooling/cli/02-build-system.md#package-roles)实现:
- 该库应导出 API 客户端服务(API client service)来调用你的插件,或导出类似结构;
- 这样,其他插件通过服务依赖(Service Ref)获得 API 客户端,而通信仍走网络层,保持插件之间的隔离边界。
这一规则正是“每个插件可视为独立微服务”架构原则的落地:可独立部署、独立扩展、独立演进。
真实案例:notifications 后端插件
仓库中的 notifications 插件(plugins/notifications-backend/src/plugin.ts)是一个很好的综合范例,它同时运用了插件、服务依赖、扩展点与配置读取:
- 插件定义:
createBackendPlugin({ pluginId: 'notifications', register(env) { ... } }); - 注册扩展点:
env.registerExtensionPoint(notificationsProcessingExtensionPoint, processingExtensions),向模块暴露addProcessor与setNotificationRecipientResolver能力; - 依赖多个服务:
init的deps同时声明了核心服务(coreServices.auth、coreServices.httpAuth、coreServices.logger、coreServices.database、coreServices.rootConfig、coreServices.scheduler)与跨插件服务(signalsServiceRef、catalogServiceRef、actionsRegistryServiceRef); - 在 init 中完成装配:创建数据库存储、挂载 HTTP 路由(
httpRouter.use(...))、配置健康检查策略、启动定时清理任务(NotificationCleaner)并注册通知相关 Actions。
从中可以清晰看到插件register阶段只做“声明”(注册扩展点、声明 init),真正的资源创建与业务装配全部推迟到init阶段,这正是新后端系统把“注册”与“初始化”分离的设计意图。
命名模式速查
插件相关的命名需要遵循统一模式(完整表格见 08-naming-patterns.md):
| 描述 | 模式 | 示例 |
|---|---|---|
| 插件导出名 | <camelId>Plugin | catalogPlugin、userSettingsPlugin |
| 插件 ID | '<kebab-id>' | 'catalog'、'user-settings' |
| 扩展点接口 | <PluginId><Name>ExtensionPoint | CatalogProcessingExtensionPoint |
| 扩展点引用 | <pluginId><Name>ExtensionPoint | catalogProcessingExtensionPoint |
| 服务接口 | <Name>Service | LoggerService、DatabaseService |
| 服务引用 | <name>ServiceRef | loggerServiceRef |
| 服务工厂 | <name>ServiceFactory | loggerServiceFactory |
核心服务的引用并不直接命名为loggerServiceRef,而是统一通过coreServices.logger形式从@backstage/backend-plugin-api导出,这也是插件中声明依赖的标准写法。
小结
Backstage 后端插件以createBackendPlugin为入口,以register阶段声明扩展点与初始化依赖、init阶段装配业务能力为基本流程,并通过默认导出约定简化安装。生产级插件必须坚持无状态/外部化存储以实现水平扩展,坚持网络通信以实现插件间隔离。理解这些机制,是编写高质量 Backstage 插件、合理拆分多后端部署的起点;关于如何通过扩展点与模块进一步丰富插件能力,请继续阅读 05-extension-points.md 与 06-modules.md。
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考