3. 软件目录建模实战
Backstage 软件目录(Software Catalog)不是一张静态清单,而是一套由实体(Entity)构成的图。每个实体都有明确的 kind、metadata、spec,并通过 relations 与其它实体建立有向关系。理解这套模型,是让门户真正“活”起来的前提。本节围绕五类核心实体、catalog-info.yaml 的编写、所有权与依赖关系,以及从 GitHub 仓库自动发现实体的完整链路展开。
五类核心实体及其职责边界
Backstage 内置的实体种类中,最常用的是以下五种,它们各自回答不同的问题:
- Component(组件):一个可部署、可独立交付的软件单元,例如一个微服务、一个前端应用、一个定时任务。它回答“这个软件是什么、谁负责、代码在哪”。
- API(接口):组件对外或对内暴露的契约,例如 REST、gRPC、GraphQL、AsyncAPI。它回答“谁提供、谁消费、契约定义在哪”。
- System(系统):一组协同工作的组件与 API 的集合,对应一个业务能力或产品边界。它回答“这些组件为什么放在一起”。
- Domain(领域):更高层的组织边界,通常对应一个团队群或业务域,用于把多个 System 归拢。它回答“这个系统属于哪块业务”。
- Resource(资源):组件依赖的基础设施或外部依赖,例如数据库、消息队列、对象存储、SaaS 服务。它回答“这个组件跑在什么之上”。
这五类实体并非平级堆叠,而是形成“Domain → System → Component → Resource”的归属链,同时 API 横向连接提供方与消费方。下图展示了这一关系模型与所有权归属:
编写 catalog-info.yaml:从最小可用到完整描述
每个实体都由一个 catalog-info.yaml 描述,通常放在仓库根目录。Backstage 通过 apiVersion、kind、metadata、spec 四个顶层字段解析它。下面是一个支付服务的完整示例:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: payment-api
description: 支付核心服务,处理下单、扣款与退款
annotations:
github.com/project-slug: acme/payment-api
backstage.io/techdocs-ref: dir:.
tags:
- java
- spring-boot
links:
- url: https://grafana.acme.com/d/payment
title: 监控大盘
spec:
type: service
lifecycle: production
owner: team-payments
system: payment-gateway
providesApis:
- payment-rest-api
dependsOn:
- resource:payment-db
- component:user-service
几个字段值得特别注意。metadata.name 在同类实体中必须全局唯一,Backstage 会用它生成实体引用(Entity Ref),格式为 kind:namespace/name,例如 component:default/payment-api。spec.owner 指向一个 Group 或 User 实体,这是所有权模型的落点。spec.lifecycle 常用取值有 experimental、production、deprecated,它直接影响门户上的健康标识。spec.type 对 Component 而言可以是 service、website、library、job 等,用于分类展示。
API 实体则用 spec.definition 内联契约,或用 $text 引用外部文件:
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: payment-rest-api
description: 支付网关对外 REST 接口
spec:
type: openapi
lifecycle: production
owner: team-payments
system: payment-gateway
definition:
$text: ./openapi/payment-v1.yaml
System 与 Domain 的写法更简洁,核心是 spec.domain 与 spec.owner:
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
name: payment-gateway
spec:
owner: team-payments
domain: payments
---
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
name: payments
spec:
owner: group:default/business-payments
Resource 用于登记组件依赖的基础设施,spec.type 可写 database、message-queue、s3-bucket 等,spec.owner 同样必填:
apiVersion: backstage.io/v1alpha1
kind: Resource
metadata:
name: payment-db
spec:
type: database
owner: team-payments
system: payment-gateway
实体关系与所有权模型
Backstage 的关系是自动推导的,不需要手写 relations 字段。当你在 Component 中写 spec.system: payment-gateway,系统会自动生成 partOf 与反向的 hasPart;写 providesApis 会生成 providesApi 与 apiProvidedBy;写 dependsOn 会生成 dependsOn 与 dependencyOf。这意味着建模时只需声明“正向”字段,反向关系由目录自动补全。
所有权模型的关键在于 spec.owner 必须指向真实存在的 Group 或 User。如果 team-payments 这个 Group 尚未导入,实体虽然能被解析,但门户上会显示“未知所有者”,且无法按团队过滤。因此推荐的做法是先用 org 类实体(Group、User)建立组织树,再让 Component、API、System、Resource 引用它们。Group 之间通过 spec.parent 与 spec.children 形成层级,从而支持“某团队下所有组件”的聚合视图。
payment-api),API 用“业务名-协议”格式(如 payment-rest-api),Resource 用“业务名-资源类型”格式(如 payment-db)。统一命名能让实体引用在 YAML 中一眼可读,也便于后续用脚本批量校验。从 GitHub 仓库自动发现实体
手工为每个仓库写 catalog-info.yaml 并逐个注册,在几十个仓库的规模下就会失控。Backstage 的自动发现机制通过 catalog.locations 配置,让目录定期扫描 GitHub 组织或仓库,自动抓取其中的 catalog-info.yaml。典型配置如下:
catalog:
locations:
- type: url
target: https://github.com/acme/payment-api/blob/main/catalog-info.yaml
- type: github-discovery
target: https://github.com/acme
rules:
- allow: [Component, API, System, Domain, Resource]
其中 github-discovery 会遍历 acme 组织下所有仓库,查找默认分支中的 catalog-info.yaml。发现到的实体进入处理流水线:先做 YAML 解析与 schema 校验,再解析实体引用、建立关系,最后写入目录数据库。整个过程是幂等的——同一个实体被多次发现只会更新,不会重复创建。
自动发现的实际价值在于“代码即目录”。开发者把 catalog-info.yaml 和代码放在同一个仓库、同一个 PR 里评审,实体定义就随代码一起演进,不会出现门户描述与代码脱节的情况。为了让发现稳定工作,需要保证三点:文件位于仓库默认分支的约定路径;metadata.name 全局唯一;spec.owner 引用的 Group 已存在。任何一条不满足,实体都会进入错误状态,可在门户的“Catalog”页面按 error 过滤查看具体原因。
Orphan 或关系缺失,优先检查 spec.owner 与 spec.system 引用的实体是否存在;若自动发现没有抓到文件,检查 GitHub 集成 Token 的仓库读取权限,以及文件是否在默认分支根目录或 rules 允许的路径内。建模顺序建议
面对一个已有几十个仓库的组织,建议按“先骨架、后填充”的顺序推进:第一步,导入 Group 与 User,建立组织树;第二步,定义 Domain 与 System,划出业务边界;第三步,为每个仓库补 catalog-info.yaml,声明 Component 及其 system、owner;第四步,抽取 API 契约并登记 API 实体;第五步,登记 Resource 并补全 dependsOn。每完成一步,门户上的关系图就会更完整一层,团队也能更早看到价值,而不是等到全部建模完成才上线。