[Backstage] 软件模板Scaffolder | 定义“Node.js微服务“
第6章:软件模板(Scaffolder)
在上一章后端插件中,我们探讨了服务器端组件如何扩展Backstage的能力,处理复杂逻辑和与外部系统的集成
这些后端功能许多用于自动化常规开发任务,其中最强大的例子之一就是软件模板。
想象公司正在快速发展,新团队不断创建新的软件组件——微服务、网站、库、数据管道等。每当新项目启动时,开发者都会面临类似的问题:
- “在这里设置新的Node.js服务的’正确’方式是什么?”
- “我应该使用哪种文件夹结构?”
- “如何配置我们的标准CI/CD流水线?”
- “我应该把
catalog-info.yaml文件放在哪里以注册到软件目录?” - “默认应该包含哪些依赖?”
这通常导致开发者复制旧项目,遗漏关键配置,或最终导致整个组织内的设置不一致。这种"重复造轮子"浪费了时间,引入错误,并使得长期维护项目更加困难。
Backstage中的软件模板功能(通常称为Scaffolder)解决了这个问题
它就像开发者门户中的主厨,只需你提供少量"食材",就能快速制作出完美标准化的新项目。它提供创建新软件组件的"配方",确保一致性、最佳实践和开发者的快速启动。
软件模板解决了什么问题?
软件模板简化了创建新软件的过程。它们提供了一种引导式、自动化的方式来启动项目,而非手动设置或复制粘贴。它们帮助我们:
- 标准化项目:确保所有新项目遵循组织的最佳实践、技术栈和结构。
- 加速开发:开发者可以在几分钟内启动新项目,预先配置好所需的一切,让他们专注于编写功能代码,而非样板代码。
- 减少错误:自动化重复的设置任务,最小化人为错误,确保关键配置(如安全设置或合规性)始终包含。
- 即时集成:自动将新组件注册到软件目录,通过TechDocs链接到文档,并从一开始就设置好CI/CD流水线。
让我们考虑一个实际用例:新开发者"Sam"需要为公司创建一个全新的"邮件通知服务"。它需要是一个标准的Node.js微服务,遵循公司关于文件夹结构的最佳实践,包含基本的CI/CD流水线,并自动注册到软件目录。软件模板如何帮助Sam快速且正确地实现这一点?
软件模板的核心概念
软件模板由几个核心思想定义:
-
模板(配方卡片):本质上,模板是描述如何创建新软件组件的YAML文件。它们包含:
- 元数据:关于模板本身的基本信息(名称、描述、所有者)。
- 参数:定义用户需要提供的信息(如项目名称、所有者、仓库URL)。Backstage用此生成用户友好的表单。
- 步骤/动作:Scaffolder后端将执行的任务序列。可能包括获取代码、修改代码、创建仓库或注册实体。
-
Scaffolder(自动化主厨):这是Backstage中的后端插件,负责获取模板、收集用户输入并执行所有定义的步骤。它协调新软件组件的创建。
-
动作(烹饪步骤):这些是Scaffolder可以执行的独立、预定义操作。可以将其视为单独的烹饪指令。例如:
fetch:template:下载代码"骨架"(基本项目结构)并应用模板。publish:github:创建新的GitHub仓库并将模板化代码推送到其中。catalog:register:将新创建的组件作为实体注册到软件目录。
Backstage提供了许多内置动作,甚至可以编写自定义动作。
-
模板引擎(智能搅拌器):在
fetch:template下载的代码"骨架"中,有特殊的占位符(如${{ values.name }})。Backstage使用模板引擎(如Nunjucks)将这些占位符替换为用户在表单中提供的值,为新项目定制代码。
解决用例:创建"邮件通知服务"
让我们看看Sam如何使用软件模板快速正确地创建"邮件通知服务":
- 导航到"创建":Sam打开Backstage门户,点击主导航中的"创建"标签。这里列出了所有可用的软件模板。
- 选择模板:Sam选择"Node.js微服务"模板,该模板用于标准化新的后端服务。
- 填写表单:出现一个表单,直接由模板的
parameters部分生成。Sam提供必要的细节:- 项目名称:
email-notification-service - 所有者:
notifications-team(使用OwnerPicker字段从软件目录拉取) - 仓库位置:
github.com/my-org/email-notification-service(使用RepoUrlPicker字段)
- 项目名称:
- 审查并创建:Sam审查输入并点击"创建"。
- Scaffolder接管:Backstage Scaffolder(一个后端插件)开始执行模板定义的
steps。 - 成功:片刻之后,过程完成。Sam看到新GitHub仓库和新注册的软件目录实体的链接。
"Node.js微服务"模板如何定义?
让我们看一个简化版的template.yaml文件示例。该文件通常位于Backstage监控的Git仓库中。
# 告诉Backstage这是一个模板及其版本
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:name: nodejs-microservice-templatetitle: Node.js微服务description: 带有基本CI/CD的标准Node.js微服务,准备部署。
spec:owner: default/platform-team # 谁维护此模板type: service # 创建什么类型的软件# 定义用户在Backstage UI中看到的输入字段parameters:- title: 提供基本信息required: ['name', 'owner']properties:name:title: 项目名称type: stringdescription: 新服务的唯一标识符(如email-notification-service)ui:autofocus: trueowner:title: 所有者type: stringdescription: 拥有此服务的团队或用户。ui:field: OwnerPicker # 从目录实体中选择的特殊UI字段ui:options:catalogFilter:kind: Group # 仅显示组作为所有者- title: 选择仓库位置required: ['repoUrl']properties:repoUrl:title: 仓库位置type: stringui:field: RepoUrlPicker # 另一个用于Git仓库的特殊UI字段ui:options:allowedHosts:- github.com # 仅允许GitHub仓库# Scaffolder后端将执行的动作steps:- id: fetch-skeletonname: 获取基础代码骨架action: fetch:template # 获取代码模板的内置动作input:url: ./skeleton # 指向模板仓库中名为'skeleton'的子文件夹values:name: ${{ parameters.name }} # 将用户输入传递给骨架owner: ${{ parameters.owner }}- id: publish-reponame: 发布到GitHubaction: publish:github # 创建并推送到GitHub的内置动作input:repoUrl: ${{ parameters.repoUrl }} # 使用用户输入的仓库URLdescription: ${{ parameters.name }}的微服务defaultBranch: 'main'- id: register-catalog-entityname: 注册到软件目录action: catalog:register # 注册到目录的内置动作input:# 从'publish-repo'步骤的输出中获取新仓库内容的URLrepoContentsUrl: ${{ steps['publish-repo'].output.repoContentsUrl }}catalogInfoPath: '/catalog-info.yaml' # 新仓库中catalog-info.yaml的路径# 成功创建后向用户显示的链接output:links:- title: 在GitHub中打开url: ${{ steps['publish-repo'].output.remoteUrl }} # 新GitHub仓库的链接- title: 在目录中打开icon: catalogentityRef: ${{ steps['register-catalog-entity'].output.entityRef }} # 新目录实体的链接
说明:
apiVersion和kind:标识此YAML为Scaffolder的Template实体。metadata:模板的基本信息。spec.owner:定义组织中谁维护此模板。spec.parameters:用户填写的"步骤"列表。每个步骤是标准的JSONSchema定义,通过ui:属性增强以自定义Backstage表单中的字段显示。name和owner:简单字符串输入。注意ui:field: OwnerPicker是一个自定义字段扩展,帮助用户从软件目录中选择现有实体。repoUrl:使用ui:field: RepoUrlPicker智能输入Git仓库URL,allowedHosts限制为github.com。
spec.steps:核心逻辑!这是Scaffolder将执行的有序action列表。fetch:template:从./skeleton目录下载样板代码,并使用用户的name和owner输入进行定制。publish:github:将fetch:template的输出推送到新的GitHub仓库,使用用户输入的repoUrl和description。catalog:register:通过指向新仓库中的catalog-info.yaml文件,将新组件注册到软件目录。它使用publish:github动作输出的repoContentsUrl。
output:定义模板运行成功后向用户显示的链接和信息。
代码骨架(./skeleton/catalog-info.yaml)
fetch:template动作引用./skeleton目录。该目录包含新服务的实际样板代码。以下是骨架中catalog-info.yaml的可能内容:
# 这是模板生成代码的一部分
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:# 这些值将由Nunjucks模板引擎填充name: ${{ values.name }} # 如'email-notification-service'description: ${{ values.name }}的微服务# 新组件的所有者(引用目录中的Group实体)owner: default/${{ values.owner }} # 如'default/notifications-team'
spec:type: servicelifecycle: experimental # 或'production'、'staging'等system: default/notification-platform # 所属的逻辑系统
说明:
- 注意
${{ values.name }}和${{ values.owner }}。这些是Nunjucks占位符。当fetch:template运行时,会用用户在表单中提供的name(email-notification-service)和owner(notifications-team)替换它们。这使得模板能动态生成定制文件。
底层原理:软件模板的旅程
当Sam为"邮件通知服务"模板点击"创建"时,Backstage的前端和后端会协调执行一系列步骤。

说明:
- Sam启动模板:Sam使用Backstage的前端插件选择模板并提供必要的输入参数(如
project name、owner、repoUrl)。 - 前端调用Scaffolder后端:Backstage前端将模板ID和收集的参数发送给
Scaffolder后端,这是一个专用于模板执行的后端插件。 - Scaffolder后端验证并启动任务:
Scaffolder后端首先根据模板的模式验证输入。如果有效,则启动新的"Scaffolder任务"以跟踪整个过程。 fetch:template动作:后端执行第一个动作fetch:template。该动作下载模板的代码"骨架"(如示例中的./skeleton目录)到临时工作区。关键的是,它使用Nunjucks模板引擎将这些骨架文件中的占位符(如${{ values.name }})替换为Sam提供的实际值(如email-notification-service)。publish:github动作:接下来执行publish:github动作。该动作从临时工作区获取完全模板化的代码,与配置的Git提供商(如GitHub API)交互,创建新仓库(如my-org/email-notification-service),并将生成的代码推送到其中。Git提供商返回新仓库的最终URL。catalog:register动作:最后运行catalog:register动作。它获取新创建的仓库URL(从publish:github步骤的输出中获取),并通过处理其catalog-info.yaml文件将其作为Component实体注册到软件目录。- 成功报告和链接:所有步骤完成后,
Scaffolder后端向前端报告任务成功,包括定义的输出链接(如新GitHub仓库的直接链接和新软件目录实体的链接)。 - 用户查看结果:Sam的Backstage UI更新,显示成功消息并提供可点击的链接,指向新创建并注册的"邮件通知服务"。
代码
支持软件模板的核心组件主要位于@backstage/plugin-scaffolder(前端)和@backstage/plugin-scaffolder-backend(后端)包中。
-
模板YAML结构:
Template实体的定义,包括apiVersion、metadata、spec.parameters、spec.steps和output,详见官方文档:- 编写模板
- 目录实体描述符格式 - Kind: Template
-
Scaffolder前端插件:这是渲染"创建"页面、列出模板、从
parameters生成表单并显示任务进度和结果的UI部分。- 通常通过在
packages/app/src/App.tsx中添加import { ScaffolderPage } from '@backstage/plugin-scaffolder';并配置其路由来启用。
- 通常通过在
-
Scaffolder后端插件:这是协调模板执行的服务器端组件。它处理
template.yaml中定义的spec.steps。- 通过在
packages/backend/src/index.ts中添加backend.add(import('@backstage/plugin-scaffolder-backend'));来启用。 - 它严重依赖后端服务进行日志记录、配置和潜在的数据库访问。
- 通过在
-
内置动作:Backstage提供了一系列强大的开箱即用动作。
fetch:template:用于下载代码骨架并应用模板逻辑。publish:github:与GitHub交互以创建仓库。这需要在app-config.yaml中正确配置集成。catalog:register:与软件目录后端通信以注册新实体。- 完整列表及安装方法见内置动作。
-
自定义动作:如果内置动作不满足需求,可以编写自定义动作以扩展Scaffolder的能力。这涉及使用
@backstage/plugin-scaffolder-node中的createTemplateAction。 -
模板语法:
template.yaml中的${{ parameters.name }}和骨架文件中的${{ values.name }}等表达式使用Nunjucks模板。这个强大的引擎允许在模板中进行动态数据注入和条件逻辑。- 更多信息见模板语法。
-
自定义字段扩展:为更定制化的用户输入,可以编写自定义字段扩展(如
OwnerPicker或RepoUrlPicker)以增强表单体验。
结论
在本章中,我们探讨了软件模板(Scaffolder),这是Backstage中改变游戏规则的功能,它自动化并标准化了新软件组件的创建
我们了解到模板由其parameters(用户输入)和steps(Scaffolder后端执行的动作)定义,这些步骤利用模板引擎生成定制代码。通过理解如何定义模板、使用内置动作和自定义用户体验,我们现在掌握了让开发者快速、一致地创建新项目并遵循组织最佳实践的工具。
接下来,我们将深入探讨另一个管理软件生态系统的强大功能:TechDocs,Backstage的"文档即代码"解决方案。
