Backstage 架构全景解析:从三大组件、插件模型到包结构与缓存配置
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
Backstage 是一个用于构建开发者门户(Developer Portal)的开源框架,本指南基于仓库内 docs/overview/architecture-overview.md 展开,系统梳理其整体架构:三大核心组成部分(Core、App、Plugins)、前端与后端构建块、数据库与缓存方案、插件的三种架构形态以及 NPM 包架构。读完本文,你将掌握 Backstage 各层组件间的依赖与通信关系,能够据此规划自己的 Backstage 应用部署形态、理解如何编写和放置插件代码,并能为生产环境正确配置数据库与缓存。
术语与三大组成部分
Backstage 被组织为三个主要组成部分,分别面向不同的贡献者群体:
| 组成 | 说明 | 面向群体 |
|---|---|---|
| Core(核心) | 开源项目中由核心开发者构建的基础功能 | 核心开发者 |
| App(应用) | 一个已部署的 Backstage 应用实例,由应用开发者(通常是组织内的生产力团队)定制与维护,集成核心功能并叠加额外插件 | 应用开发者 |
| Plugins(插件) | 为增强 Backstage 应用而提供的附加功能,可以是公司内部专用的,也可以是开源的、可复用的 | 插件开发者 |
这套术语贯穿整个 Backstage 文档体系:例如本仓库中的packages/目录承载了 Core 层的核心包(packages/core-components、packages/frontend-plugin-api、packages/backend-defaults等),plugins/目录则容纳了软件目录、Scaffolder、TechDocs 等众多官方插件。
整体架构总览:前端、后端与数据库
在实际环境中运行 Backstage 架构,通常需要将组件容器化,仓库提供了多种命令辅助完成这一过程。架构中包含 3 个主要组件:
- 前端(Frontend):包含核心 Backstage 用户界面(UI),UI 本身是一个扩展,直接与用户交互,呈现来自核心功能插件以及用户添加的其他插件的信息。
- 后端(Backend):包含后端插件、核心服务以及其他服务,是 Backstage 的服务端部分,负责把各个部件"接线"在一起。你可以按扩展和隔离单个功能的需要,部署多个后端、多个后端容器。
- 数据库(Databases):承载你的 Backstage 数据。
这套架构与仓库中的packages/backend-defaults/src/CreateBackend.ts(createBackend的入口)以及packages/frontend-app-api、packages/frontend-defaults等包一一对应:后端由backend包组装启动,前端由app包组装渲染,两者通过 HTTP 与数据库交互。
前端构建块(Frontend Building Blocks)
前端架构图展示了各个构建块以及它们之间相互交互的其他构建块。
App
App 就是你自己创建的应用实例,它是 Backstage 前端应用的根。App 本身不具备任何直接功能,只负责把各个部件"接线"在一起——将内置扩展、插件扩展与覆盖扩展组装成一棵应用扩展树(app extension tree),整个应用据此实例化并渲染。
扩展(Extensions)
扩展是构建应用视觉与非视觉结构的构建块。既存在 App 自身提供的内置扩展,也存在插件提供的扩展。每个扩展都挂载到一个父级上并与之共享数据,也可以拥有任意数量的子扩展。扩展 ID 遵循[<kind>:][<namespace>][/][<name>]的构造模式,输出数据通过扩展数据引用(Extension Data Reference)传递,输入则由父扩展按需声明。
用户界面(User Interface)
UI 是前端中的一种扩展,是围绕一组插件的轻量级客户端包装器,提供一些核心 UI 组件和用于配置管理等共享活动的库。下图高亮了 UI 中哪些部分由核心(core)提供、哪些来自插件(plugin):
每个插件通常都会在一个专属 URL 上向 UI 注册自己,例如服务目录(Service Catalog)插件注册在/catalog路径。
前端插件(Frontend Plugins)
插件提供应用中的实际功能,体量可以从一个微型组件到一整个可供其他插件组合集成的新系统。插件既可以完全独立,也可以相互叠加以扩展现有插件;插件之间通过组合各自的扩展、共享 Utility API 与路由来通信。
Backstage 自带一组核心插件:
- Software Catalog(软件目录):集中式系统,保存所有软件(服务、网站、库、ML 模型、数据管道等)的元数据,也可包含运行软件所需的物理或虚拟基础设施元数据,可通过 UI 查看与搜索。
- Software Templates(软件模板):帮助在 Backstage 内创建组件的工具,可加载代码骨架、包含变量,并把模板发布到 GitHub 等位置。
- TechDocs:内置的 "docs-like-code" 解决方案,文档以 Markdown 编写并与代码一同存放。
- Kubernetes:允许开发者检查其服务健康状况的工具,无论服务位于本地主机还是生产环境。
- Search:在 Backstage 生态中搜索信息,可定制每个搜索结果的观感,也可接入自己的搜索引擎。
在仓库中,这些插件的实现位于plugins/catalog、plugins/scaffolder、plugins/techdocs、plugins/kubernetes、plugins/search等目录,前后端各成体系。
扩展覆盖(Extension Overrides)
除了内置扩展和插件提供的扩展,还可以安装扩展覆盖:一组具有高优先级的扩展,能够替换现有扩展。例如可以用来覆盖插件提供的某个扩展,或安装一个全新的扩展(如新的应用主题)。
Utility APIs
Utility APIs 提供的功能让插件构建更简单、让插件之间可以共享功能,同时也作为集成者改变应用行为的一处定制点。每个 Utility API 由 TypeScript 接口以及一个用于访问实现的引用(reference)定义;其实现由扩展定义,与其他扩展一样可以被覆盖。
路由(Routes)
Backstage 路由系统增加了一层间接性,使插件无需显式知道其他扩展渲染在哪个 URL 路径、甚至无需知道其是否存在,就能互相路由。它让插件可以互相共享路由、在运行时动态生成具体链接。链接到实际 URL 的解析由 App 负责,集成者也可以定义自己的路由绑定来决定如何解析链接;路由系统还支持插件定义内部路由,便于在同一插件内链接到不同内容。
后端构建块(Backend Building Blocks)
后端架构图展示了各个构建块及其交互关系。
后端实例(Backend)
后端实例本身是部署单元,自身不提供任何功能,只负责"接线"。你可以决定部署多少个后端:把所有功能放进单个后端,或按扩展与隔离需求拆分为多个更小的部署。
典型的后端入口代码如下(对应仓库packages/backend-defaults中的createBackend):
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();createBackend负责装配提供给应用的所有功能,并为插件提供全部核心服务的默认实现。创建后端时不做实际工作,一切延迟到backend.start()调用:启动时后端会校验所有功能(例如确保没有循环依赖)。底层上,createBackend调用@backstage/backend-app-api的createSpecializedBackend,后者负责在没有任何服务或功能的情况下真正创建后端实例——createBackend是"开箱即用(batteries included)"的高层入口,createSpecializedBackend则更底层。仓库源码见 packages/backend-defaults/src/CreateBackend.ts。
此外你可以在一个项目中创建多个后端,例如只启用 catalog 插件的后端与只启用 scaffolder 插件的后端分开部署、独立扩容。
后端插件(Backend Plugins)
插件提供实际功能。它们完全独立运行:若插件之间要通信,只能通过网络进行,不允许通过代码直接通信。因此每个插件都可以被视为一个独立的微服务。插件使用createBackendPlugin创建,必须拥有与包名一致的pluginId(去掉-backend后缀)以及register方法:
// 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'); }, }); }, });插件生态遵循"可扩展(Scalable)"与"隔离(Isolated)"两条规则:插件必须设计为可水平扩展(不保存内存状态,或确保状态可在多实例间复制,通常存入外部服务如数据库);插件绝不通过代码直接通信,需要对外暴露接口时应通过 node-library 包导出 API 客户端服务。
服务(Services)
服务提供工具以简化插件的实现,让每个插件不必从零实现一切。系统内置了大量核心服务(日志、数据库访问、读取配置等),也可以导入第三方服务或创建自己的服务。服务通过createServiceRef定义的引用 +createServiceFactory定义的工厂实现,构成一套依赖注入机制,每个后端实例就是依赖注入容器。
后端默认提供丰富的核心服务,统一通过@backstage/backend-plugin-api的coreServices命名空间访问,例如:
Auth Service—— Token 认证与凭据管理Cache Service—— 用于缓存的键值存储Database Service—— 基于 Knex 的数据库访问与管理Discovery Service—— 插件间通信的服务发现Http Router Service—— 插件的 HTTP 路由注册Logger Service/Root Logger Service—— 插件级 / 根级日志Scheduler Service—— 分布式后台任务调度Url Reader Service—— 从外部系统读取内容Permissions Service、Metrics Service、Tracing Service等
服务还作为后端安装的定制点:你可以用自研实现覆盖服务,也可以对现有服务做小幅定制。服务作用域默认是'plugin'(每个插件获得独立实例),另有'root'作用域(跨插件共享、总是初始化)。仓库中核心服务定义位于 packages/backend-plugin-api/src/services,默认实现位于packages/backend-defaults。
扩展点(Extension Points)
许多插件提供了可扩展方式,例如 Catalog 的实体提供者(entity providers)、Scaffolder 的自定义动作(custom actions),这些扩展模式现在被编码为扩展点(Extension Points)。扩展点看起来与服务类似(同样通过引用依赖),关键区别在于:扩展点由插件或模块自己注册和提供,取决于各自想暴露哪些定制;扩展点从插件/模块实例中单独导出,且可同时暴露多个——这样比维护单一庞大 API 表面更容易逐个演进和弃用。
以 Scaffolder 的 actions 扩展点为例:
import { createExtensionPoint } from '@backstage/backend-plugin-api'; export interface ScaffolderActionsExtensionPoint { addAction(action: ScaffolderAction): void; } export const scaffolderActionsExtensionPoint = createExtensionPoint<ScaffolderActionsExtensionPoint>({ id: 'scaffolder.actions', });模块(Modules)
模块使用扩展点向其他插件或模块添加新功能,例如添加单个 Catalog 实体提供者、或一个或多个 Scaffolder 动作。每个模块只能使用属于单个插件的扩展点,且必须与该插件部署在同一后端实例中;模块只能通过注册的扩展点与其插件或其他模块通信。与插件一样,模块也能访问服务并依赖自己的服务实现,但它们与所扩展的插件共享服务——不存在模块专属的服务实现。
模块创建示例如下(为 Catalog 添加自定义处理器):
// plugins/catalog-backend-module-example-processor/src/module.ts import { createBackendModule } from '@backstage/backend-plugin-api'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; export const catalogModuleExampleCustomProcessor = createBackendModule({ pluginId: 'catalog', moduleId: 'example-custom-processor', register(env) { env.registerInit({ deps: { catalog: catalogProcessingExtensionPoint, logger: coreServices.logger, }, async init({ catalog }) { catalog.addProcessor(new MyCustomProcessor(logger)); }, }); }, });模块初始化时序有严格保证:为保证模块在插件启动前注册完所有扩展,每个插件的所有模块会先于插件被完全初始化,即所有模块init方法返回的 Promise 必须在插件init调用前 resolve;initresolve 之后便不能再与扩展点交互。
数据库
数据库承载 Backstage 数据。Backstage 后端及其内置插件基于 Knex 库,并为每个插件建立独立的逻辑数据库,从而获得良好的隔离性,各插件可以独立执行迁移、独立演进。
Knex 支持多种数据库,但截至写作时 Backstage 主要针对两种进行测试:
- SQLite—— 主要用作内存 mock/测试数据库
- PostgreSQL—— 首选的生产数据库
其他数据库(如 MySQL 系列)据报可以工作,但尚未被充分测试。为 Backstage 实例配置 PostgreSQL 的步骤见 Database,为插件配置数据库见 Configuring Plugin Databases。
插件架构:三种形态
从架构上看,插件可以采取三种形态:独立插件、服务后端插件、第三方后端插件。
独立插件(Standalone Plugins)
独立插件完全在浏览器中运行。例如 Tech Radar 插件只是渲染硬编码信息,不向其他服务发起任何 API 请求。将 Tech Radar 安装进 Backstage 应用只需把它作为前端插件加入即可:
注:以下示意图未展示前端和后端容器的详细内容,以突出与添加指定插件相关的变更。
插件添加完成后,即可在 Backstage UI 中查看 Tech Radar 信息。
服务后端插件(Service Backend Plugins)
服务后端插件会向运行 Backstage 的组织职权范围内的服务发起 API 请求。例如 Lighthouse 插件向lighthouse-audit-service发起请求,后者是一个运行 Google Lighthouse 库副本并把结果存入 PostgreSQL 数据库的微服务。Lighthouse 插件被添加到前端,而lighthouse-audit-service容器已公开在 Docker Hub 上,可下载并运行:
docker run spotify/lighthouse-audit-service:latestBackstage 中的软件目录是另一个服务后端插件示例:它从 Backstage 后端服务检索一组服务("实体"),并在表格中渲染给用户。
第三方后端插件(Third-party Backend Plugins)
第三方后端插件与服务后端插件类似,主要区别在于支撑该插件的服务托管在托管 Backstage 的公司生态之外。CircleCI 插件即为一例:CircleCI 是 SaaS 服务,可完全不了解 Backstage 地独立使用,它提供 API 供 Backstage 插件消费以展示内容。
从用户浏览器发往 CircleCI 的请求会经过 Backstage 提供的代理服务(proxy)。若没有它,这些请求会被跨域资源共享(CORS)策略拦截——CORS 会阻止托管在https://example.com的浏览器页面加载托管在https://circleci.com的资源。
包架构(Package Architecture)
Backstage 高度依赖 NPM 包,既用于库的分发,也用于项目内代码的组织。虽然如何组织 Backstage 项目由你决定,但存在一组既定的推荐模式,可帮助建立合理的项目结构并在不同 Backstage 项目间保持熟悉感。
上图从单个插件及其可能包含的全部包(粗边框、斜体字)出发,环绕插件的是不同的包组,即插件的不同接口点。箭头表示对目标包代码的运行时依赖,此严格依赖图仅适用于运行时dependencies(为测试目的,devDependencies可以打破该表的规则)。app与backend包是 Backstage 项目的入口:app包是把一组前端插件组装起来并按组织定制的应用,backend包是为 Backstage 应用提供动力的后端服务。一个项目中可以有多个此类包,尤其backend包可拆分为更小的部署单元,各自服务自己的目的、承载更少的插件。
插件包(Plugin Packages)
一个典型插件由最多五个包组成:两个前端包、两个后端包、一个 isomorphic(同构)包。插件内所有包必须共享公共前缀,通常形如@<scope>/plugin-<plugin-id>,backstage-plugin-<plugin-id>或@<scope>/backstage-plugin-<plugin-id>也是合法变体。在前缀之外,每个包还有标识其角色的唯一后缀。除这五个插件包外,插件还可以有额外的前端/后端模块,可安装以启用可选功能。完整后缀清单与角色说明见 Plugin Package Structure ADR。
-react、-common、-node三个插件包共同构成插件的外部库(plugin library)。插件库让其他插件可以在此基础上构建和扩展本插件,也让本插件能够依赖和扩展其他插件。因此插件库包应当允许被重复安装(版本混装很常见);插件也禁止直接导入其他插件的非库包,插件间的所有通信都必须通过库和应用本身进行。
前端包(Frontend Packages)
前端包分为两大组。第一组是 "Frontend App Core",即仅由app包使用的一组包,帮助搭建应用核心结构并为插件库提供可依赖的基础。第二组是其余共享包,进一步分为 "Frontend Plugin Core" 与 "Frontend Libraries":核心包被认为特别稳定,构成前端框架的核心,其最重要职责是围绕每个插件形成边界,并提供把一组插件组合成运行中应用的工具集;其余前端包是更传统的库,作为构建插件的积木。
后端包(Backend Packages)
后端库包目前并不像前端包那样共享类似的插件架构,它们只是一组帮助构建后端服务的积木与模式。不过这一状况未来很可能改变。
公共包(Common Packages)
公共包是事实上被所有其他包依赖的包,数量小但渗透性强。由于公共包是同构(isomorphic)的、必须在前端和后端都能执行,它们永远不允许依赖任何前端或后端包。Backstage CLI 自成一类,几乎被所有其他包依赖;但它本身不是库,必须始终只是开发依赖(devDependency)。
如何决定代码放置位置
有时很难决定插件代码放在哪里——例如应该直接放在-backend插件包中还是-node包中?通用准则是:尽量降低代码的暴露程度。如果不必成为公共 API,就最好不要暴露;如果不需要被其他插件使用,就把它直接放在插件包里。
缓存(Cache)
Backstage 后端及其内置插件还可以利用缓存存储来提升性能或可靠性。与数据库类似,插件会获得逻辑隔离的缓存连接,底层由 Keyv 驱动。截至写作时,Backstage 可配置使用五种缓存存储之一:
| store 取值 | 适用场景 |
|---|---|
memory | 主要用于本地开发 |
memcache | 生产部署选项之一 |
redis | 生产部署选项之一 |
valkey | 生产部署选项之一 |
infinispan | 生产部署选项之一 |
memory主要用于本地开发;生产部署推荐使用其余存储之一。哪种缓存存储适合你的 Backstage 实例,取决于你自己的运行时约束以及所运行插件的需求。
使用 memory 作为缓存
backend: cache: store: memory使用 memcache 作为缓存
backend: cache: store: memcache connection: user:pass@cache.example.com:11211使用 Redis 作为缓存
backend: cache: store: redis connection: redis://user:pass@cache.example.com:6379使用 Infinispan 作为缓存
最小配置:全部使用默认值,不配置authentication,期望缓存名为cache、主机为127.0.0.1:11222:
backend: cache: store: infinispan扩展配置:与 Redis 不同,Infinispan不会自动创建缓存——需要你预先在 infinispan 服务器中配置好缓存。完整的配置项列表(包括备份集群支持)参见 Infinispan HotRod JavaScript 客户端 API 文档:
backend: cache: store: infinispan infinispan: servers: - host: 127.0.0.1 port: 11222 cacheName: backstage-cache mediaType: application/json authentication: enabled: true userName: yourusername password: yourpassword saslMechanism: PLAIN欢迎贡献对其他缓存存储的支持。
小结
Backstage 的架构可以用"三个组件、两套构建块、一条插件主线"来概括:Core/App/Plugins 三大组成划分了职责边界;前端以应用扩展树为核心,通过扩展、Utility API 与路由系统实现插件间的组合与间接通信;后端以独立插件为微服务单元,通过服务(依赖注入)与扩展点(模块扩展)实现解耦与定制;数据库基于 Knex 按插件逻辑隔离,缓存基于 Keyv 支持五种存储。插件三种形态(独立、服务后端、第三方后端)决定了其部署与网络边界,而 NPM 包架构则为代码组织提供了"低暴露、按角色分包"的明确指引。理解这些构建块,是规划多后端拆分、编写新插件、为生产环境落地 Backstage 的第一步。
【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考