Backstage Catalog Client 完全指南:前后端同构的软件目录 API 客户端
Backstage Catalog Client 完全指南前后端同构的软件目录 API 客户端【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstagebackstage/catalog-client是 Backstage 提供的、前后端通用的软件目录CatalogAPI 客户端包。无论你是在后端插件中直接读写目录数据还是需要理解前端catalogApiRef背后真正的 HTTP 通信机制这个包都是必经之路。读完本文你将掌握CatalogClient的完整方法面、过滤器与分页游标语法、谓词查询、流式读取以及如何在测试中借助InMemoryCatalogClient与 MSW 编写可靠的客户端测试。定位前后端同构的 Catalog 通信层在 Backstage 的整体架构中软件目录Software Catalog是所有实体Entity的中央数据源。而 packages/catalog-client 就是客户端与服务端之间的通信层包含一个前端和后端均可用的客户端用于与 Backstage Catalog 后端通信后端代码可以直接 import 并使用本包例如在后端插件、任务或脚本中读取目录数据前端代码通常不直接实例化客户端而是使用backstage/plugin-catalog-react包导出的catalogApiRef把它当作其他前端 Utility API 一样注入使用。从 packages/catalog-client/package.json 可以看到该包的官方描述是An isomorphic client for the catalog backend角色为common-library被列为sideEffects: false说明它可以在 Node.js 与浏览器环境间共享同一份实现。其依赖包括backstage/catalog-model实体模型与 ref 解析、backstage/errors错误类型、backstage/filter-predicates谓词过滤以及cross-fetch跨环境 fetch 实现。包的入口packages/catalog-client/src/index.ts只导出两样东西CatalogClient类与types中的全部类型干净且聚焦。快速上手实例化与最小请求CatalogClient的构造函数只接受两个可选/必填依赖定义见 packages/catalog-client/src/CatalogClient.tsconst client new CatalogClient({ discoveryApi: { async getBaseUrl(pluginId: string): Promisestring { // 返回目录插件的基础地址通常是 /api/catalog return http://localhost:7007/api/catalog; }, }, fetchApi?: { fetch: typeof fetch }, // 可选默认使用全局 fetch });discoveryApi必填提供getBaseUrl(pluginId)方法用于解析后端服务地址。在真实应用中通常传入backstage/core-plugin-api的discoveryApiRef客户端会以pluginId为catalog拼接出/api/catalog基础路径。fetchApi可选允许注入自定义的 fetch 实现例如带认证头、限流或日志的封装。从源码结构看该选项会被透传给内部的DefaultApiClient由 OpenAPI 生成的客户端见 packages/catalog-client/src/schema/openapi。最小示例——读取目录里的全部组件const response await client.getEntities({ filter: [{ kind: component }], });需要携带后端 token 时所有方法都接受第二个参数CatalogRequestOptions其中唯一字段是token见 packages/catalog-client/src/types/api.tsconst response await client.getEntities({}, { token: my-service-token });CatalogApi 接口一张完整的方法清单CatalogClient实现的是CatalogApi接口定义见 packages/catalog-client/src/types/api.ts全部方法可归纳为四组分组方法说明实体查询getEntities列出目录实体支持过滤、字段投影、排序、偏移分页getEntitiesByRefs按一组 entity ref 批量获取实体顺序与输入一致未命中的位置返回undefinedgetEntityByRef/getEntityByName按 ref 三元组kind/namespace/name获取单个实体后者已废弃建议改用前者queryEntities游标分页查询支持全文检索与谓词查询streamEntities基于queryEntities的异步流式读取逐页 yieldgetEntityAncestors获取实体的祖先链父实体层级getEntityFacets统计实体某字段的取值分布facets实体维护refreshEntity标记实体重新处理reprocessremoveEntityByUid按 UID 删除实体validateEntity校验实体及其 location 是否合法Location 管理getLocations/queryLocations/streamLocations列出、分页查询、流式读取已注册的 locationgetLocationById/getLocationByRef/getLocationByEntity按 ID、ref、实体反查 locationaddLocation/updateLocation/removeLocationById注册、更新、删除 location分析analyzeLocation分析一个目标地址返回其中可生成的实体注册前预览其中getEntityByName在源码中明确标记为 deprecated注释里还保留了一段建议移除日期为 2022 年 8 月的历史备注说明它属于长期兼容存留的旧接口新代码一律使用getEntityByRef。过滤器语法EntityFilterQuery 与 CATALOG_FILTER_EXISTS过滤是目录查询中最常用的能力其类型定义packages/catalog-client/src/types/api.ts在语义上非常讲究每个 key 是实体结构中的点分路径例如metadata.name匹配时大小写不敏感多个 filter 对象组成的数组之间是 OR同一个 filter 对象内部的不同 key 之间是 AND同一个 key 下的多个 value 之间是 OR。官方示例const response await client.getEntities({ filter: [ { kind: [API, Component] }, { metadata.name: a, metadata.namespace: b }, ], });等价语义为(kind API OR kind Component) OR (metadata.name a AND metadata.namespace b)存在性断言CATALOG_FILTER_EXISTS除字面值匹配外包还导出一个特殊符号CATALOG_FILTER_EXISTS用于断言该 key 存在无论其值是什么。它的实现是一个带随机 UUID 的Symbol.for(...)见 packages/catalog-client/src/types/api.tsSymbol.for保证跨模块边界也是同一个符号import { CATALOG_FILTER_EXISTS } from backstage/catalog-client; const response await client.getEntities({ filter: [{ metadata.annotations.backstage.io/orphan: CATALOG_FILTER_EXISTS }], });底层如何编码为查询参数在 packages/catalog-client/src/CatalogClient.ts 的私有方法getFilterValue中可以看到完整的编码逻辑外层数组anyOf被编码为多次出现的filter查询参数内层allOf用逗号拼接。例如注释中给出的等价 URL 形态/api/catalog/entities?filtermetadata.namewayback-search,kindcomponentfiltermetadata.namewww-artist,kindcomponent这一点在 packages/catalog-client/src/CatalogClient.test.ts 的用例中也得到了验证传入{ a: 1, b: [2,3], ö: }等过滤器后测试断言实际发出的filter参数为a1,b2,b3,ö而CATALOG_FILTER_EXISTS则被编码为不带等号的裸 key如c。分页查询queryEntities 与游标相比getEntities的 offset 分页queryEntities提供基于**游标cursor**的分页更适合大数据集与可来回翻页的 UI。初始请求packages/catalog-client/src/types/api.ts支持filter传统键值过滤query谓词过滤见下一节limit/offset页大小与偏移orderFields排序指令fullTextFilter全文检索{ term, fields? }fields字段投影totalItemsinclude默认计算总数或exclude跳过计数响应的totalItems为 0适合不需要精确总数的游标分页 UI。官方示例——查询所有 name 以A开头、kind 为group的实体按名称升序排列const response await catalogClient.queryEntities({ filter: [{ kind: group }], limit: 20, fields: [metadata, kind], fullTextFilter: { term: A }, orderFields: { field: metadata.name, order: asc }, }); // 取下一页 const secondBatch await catalogClient.queryEntities({ cursor: response.pageInfo.nextCursor, limit: 20, fields: [metadata, kind], });响应结构packages/catalog-client/src/types/api.ts包含items、totalItems与pageInfonextCursor/prevCursor游标因此可以前后双向翻页。游标的实现细节在 packages/catalog-client/src/utils.ts 中可以看到游标是 base64 编码的 JSON 对象客户端通过cursorContainsQuery解码游标并检查其中是否携带query字段——这决定了翻页请求该走 GET 还是 POST 端点。源码里还留有一段 TODO 注释指出这种窥探游标内容的方式成本较高且游标并非不透明未来希望统一 GET/POST 的游标格式、仅凭游标大小决定走哪个端点。谓词查询Predicate Query对于filter难以表达的复杂逻辑queryEntities与getEntitiesByRefs支持query参数使用逻辑运算符谓词逻辑组合$all、$any、$not匹配运算符$exists、$in、$hasPrefix、$contains部分支持。典型用法kind为 component且spec.type属于 service 或 websiteconst response await client.queryEntities({ query: { $all: [ { kind: component }, { spec.type: { $in: [service, website] } }, ], }, });当filter与query同时提供时二者会被$all合并见 packages/catalog-client/src/utils.ts 的convertFilterToPredicate以及 packages/catalog-client/src/CatalogClient.ts 的合并逻辑。从实现来看携带谓词的请求会路由到 POST 端点queryEntitiesByPredicate而传统 filter 走 GET 端点——这是客户端在 packages/catalog-client/src/CatalogClient.ts 中根据请求形态自动选择的。流式读取streamEntities 与 streamLocations当需要处理大量实体而不想一次性载入内存时streamEntities和streamLocations提供了 AsyncIterable 形式的逐页流式读取for await (const batch of client.streamEntities({ filter: [{ kind: api }] })) { for (const entity of batch) { // 处理每一批实体 } }默认每页大小为DEFAULT_STREAM_ENTITIES_LIMIT 500packages/catalog-client/src/constants.ts可通过pageSize覆盖每页数量实现上两者都是循环调用queryEntities/queryLocations并把pageInfo.nextCursor作为下一次请求的游标直到没有下一页为止见 packages/catalog-client/src/CatalogClient.ts。streamLocations的默认页大小则来自DEFAULT_STREAM_LOCATIONS_LIMIT 500同样定义于 packages/catalog-client/src/constants.ts。字段投影与排序字段投影fields传入点分路径数组响应只包含这些路径其余字段被剔除。例如[kind, metadata.annotations]的响应形如{ kind: Component, metadata: { annotations: {...} } }。这在只关心少量字段、希望减小响应体积时非常有用也常用于列表页。排序orderEntityOrderQuery支持一或多个{ field, order: asc | desc }指令多个指令按优先级递减后者仅在前者值相等时生效且大小写不敏感。一个容易忽略的行为是对不存在该字段的实体无论升降序它们总是排在最后见 packages/catalog-client/src/types/api.ts 的说明。批量获取getEntitiesByRefsgetEntitiesByRefs允许用一次请求按 ref 列表取回多个实体返回数组与请求的 refs顺序一致未命中的位置是undefined支持fields投影、filter与query过滤filter与query同时提供时以$all合并。底层实现有个值得注意的细节splitRefsIntoChunkspackages/catalog-client/src/utils.ts会把 refs 拆成多个小块每块不超过 1000 个 ref且 JSON 编码后的字符串长度不超过约 90 KiB以避免超过 Express 默认 100 KiB 的请求体限制。这也解释了为什么该方法返回的实体顺序始终与输入一致——它按顺序分块请求再顺序拼接。前端还是后端选对使用姿势这是 README 反复强调的关键决策点后端代码直接import { CatalogClient } from backstage/catalog-client并自行实例化配合discoveryApi与可选的fetchApi前端代码不要直接 new 一个CatalogClient。应使用backstage/plugin-catalog-react导出的catalogApiRef通过useApi(catalogApiRef)获取实例。这样能保证与 Backstage 前端的依赖注入、认证与插件系统正确集成。测试基础设施InMemoryCatalogClient该包还提供开箱即用的内存测试替身InMemoryCatalogClient导出路径backstage/catalog-client/testUtils入口见 packages/catalog-client/src/testUtils.ts实现见 packages/catalog-client/src/testUtils/InMemoryCatalogClient.tsimport { InMemoryCatalogClient } from backstage/catalog-client/testUtils; const client new InMemoryCatalogClient({ entities: [myMockEntity], }); const { items } await client.getEntities({ filter: [{ kind: component }] });它的能力与限制都很明确支持实体的过滤含CATALOG_FILTER_EXISTS、排序、offset/游标分页、全文检索、字段投影以及实体的按 ref 获取、按 UID 删除、facets 统计、祖先查询不支持location 相关方法与analyzeLocation/validateEntity会抛出NotImplementedError实现细节上它复用了 catalog-backend 的buildEntitySearch遍历逻辑从 plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch 引入以保证内存过滤语义与真实后端一致由于CATALOG_FILTER_EXISTS是 Symbol、无法直接 JSON 序列化它还会在游标编解码时用哨兵字符串替换见 packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts。如果希望针对真实CatalogClient的 HTTP 行为写测试仓库里的 packages/catalog-client/src/CatalogClient.test.ts 是很好的范本它用 MSWMock Service Worker拦截请求断言具体的请求 URL、查询参数、请求体与响应解析覆盖了过滤编码、分页游标、错误处理等场景。小结backstage/catalog-client虽然只是一个客户端包但它承载了 Backstage 目录体系的完整 API 契约从CatalogApi接口的 20 余个方法到 OR/AND 语义的过滤器、双向游标分页、谓词查询、流式读取再到面向测试的内存实现均在此处定义并实现。理解它的设计前端走catalogApiRef、后端直连、getFilterValue的 URL 编码、splitRefsIntoChunks的分块策略能让你在编写插件、定制目录功能或排查目录查询问题时事半功倍。相关类型与实现的权威定义可继续查阅 types/api.ts、CatalogClient.ts 与 utils.ts。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考