尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Backstage 插件 OpenAPI Schema-First 开发实战:从规范到类型化 Router 与自动生成 Client

Backstage 插件 OpenAPI Schema-First 开发实战:从规范到类型化 Router 与自动生成 Client Backstage 插件 OpenAPI Schema-First 开发实战从规范到类型化 Router 与自动生成 Client【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage导读本文面向 Backstage 插件开发者介绍如何基于 OpenAPI 规范以 Schema-First规范优先的方式驱动插件后端与前端客户端的开发。通过本教程你将掌握在 Backstage 插件中存放与校验 OpenAPI 规范、从规范生成带强类型约束的 Express Router 与自动生成的 API Client、以及借助测试流量反向验证规范与实现一致性的完整工作流让规范真正成为插件生命周期中的单一事实来源。该方案由 Backstage OpenAPI 工具项目OpenAPI tooling project area提供相关命令统一封装在backstage/repo-tools包中属于实验性Experimental能力。当前仓库中backstage/backend-openapi-utils包即为运行时支撑实现可用于对照学习。这套工具能为你带来什么目标非常明确让 OpenAPI 规范与插件生命周期更紧密地耦合具体提供三类能力类型化的 Express Router根据规范生成带强类型约束的expressRouter在开发阶段为输入与输出值提供强力护栏guardrails。支持 query、path 参数和 request body 的完整类型推导同时对 headers 与 cookies 提供实验性支持。自动生成的客户端基于规范生成与插件后端交互的客户端代码覆盖所有请求类型、参数、body 与返回类型并提供低层接口以便更高层的库在此基础上做定制。校验与验证工具确保 API 实现与规范始终保持同步包括在单元测试阶段对请求/响应进行实体验证。从源码结构看这一系列命令集中在 packages/repo-tools/src/commands/package/schema/openapi面向单个插件包与 packages/repo-tools/src/commands/repo/schema/openapi面向整个仓库两个目录下分别提供generate、validate、diff、fuzz、lint等子命令。前置条件技术要求本教程假定你已经具备以下基础会构建一个 Backstage 插件熟悉Express.js与TypeScript了解 OpenAPI 3.1 Schema 规范。OpenAPI 版本支持Backstage 同时支持 OpenAPI 3.0 与 3.1 规范。如果已有 3.0 的存量规范官方建议迁移到 3.1可以使用oasdiff upgrade spec.yaml自动完成转换。主要变化包括将nullable: true替换为type: [string, null]或改用anyOf/oneOf从 path 参数中移除allowReserved在 3.1 中该属性仅对 query/cookie 参数有效。值得注意的是packages/backend-openapi-utils/README.md 中声明该运行时包只支持 OpenAPI 3.1 规范因此将规范统一升级到 3.1 能获得最完整的工具链支持。环境搭建在工作区根目录安装backstage/repo-tools。该包包含插件所需的全部 OpenAPI 相关命令后续教程会反复使用yarn add --dev backstage/repo-tools另外若要使用破坏性变更检测package schema openapi diff命令还需要在系统上安装oasdiffCLI同时请确保java可执行文件在 PATH 中OpenAPI Generator 依赖 JVM 运行。存放你的 OpenAPI 规范规范文件应当放在后端插件的src/schema目录下。例如给 catalog 插件添加规范时需要在plugins/catalog-backend下新建src/schema目录即plugins/catalog-backend/src/schema并在其中放置openapi.yaml文件。目前仅支持.yaml扩展名.yml不被支持。这一约定在源码中有明确对应packages/repo-tools/src/lib/openapi/constants.ts中定义了YAML_SCHEMA_PATH src/schema/openapi.yaml所有 OpenAPI 命令都会按此路径定位当前插件的规范文件。同时该文件还定义了生成产物的输出路径OUTPUT_PATH src/schema/openapi/generated以及旧版单文件产物路径OLD_SCHEMA_PATH src/schema/openapi.generated.ts——如果你曾使用旧版生成方式新版工具会自动检测并清理这个旧文件。仓库内 docs/openapi/definitions/auth.yaml 提供了一份真实的 OpenAPI 3.0.1 示例规范对应backstage/auth-backend的 auth-provider API其中展示了 query 参数、header 参数、cookie 参数、oneOf响应体以及 components/schemas 定义等写法可作为编写规范的参考样例。校验你的规范编写完openapi.yaml后可以在插件目录下运行以下命令验证它是否是一份结构上合法的 OpenAPI 3.x 文档yarn backstage-repo-tools package schema openapi validate该命令会检查规范能否被正确解析并符合 OpenAPI 规范。建议在任何从规范生成代码的操作之前先执行它。底层实现可参考 packages/repo-tools/src/commands/package/schema/openapi/validate.ts它调用getPathToCurrentOpenApiSpec()定位当前插件的src/schema/openapi.yaml再通过loadAndValidateOpenApiYaml()完成解析与校验成功时输出绿色提示OpenAPI spec is valid.失败时以非零退出码结束并打印错误详情。规范风格与最佳实践检查如果需要针对风格和最佳实践做 lint 检查可额外运行注意这里是仓库级repo命令yarn backstage-repo-tools repo schema openapi lint从规范生成类型化的 Express Router在插件目录下运行yarn backstage-repo-tools package schema openapi generate --server该命令会在src/schema/openapi/generated目录下生成一个router.ts文件其中包含内嵌的 OpenAPI 规范以as const形式导出的spec常量以及一个工厂函数createOpenApiRouter用于创建与规范类型完全匹配的 Express Router。建议把这条命令写入你的package.json以便复用也可以将服务端与客户端生成合并成一条命令yarn backstage-repo-tools package schema openapi generate --server --client-package clientPackageDirectory在插件中接入生成的 Router修改插件的router.ts或createRouter.ts接入生成的 Router import { createOpenApiRouter } from ../schema/openapi; - import Router from express-promise-router; ... export async function createRouter( options: RouterOptions, ): Promiseexpress.Router { const router await createOpenApiRouter(); - const router Router();生成原理与源码视角从 packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts 可以看到生成过程的完整链路读取src/schema/openapi.yaml通过openapitools/openapi-generator-cli的typescript生成器配合仓库内模板 templates/typescript-backstage-server.yaml 生成基础类型与EndpointMap生成src/schema/openapi/generated/router.ts其核心是调用backstage/backend-openapi-utils包导出的createValidatedOpenApiRouterFromGeneratedEndpointMapEndpointMap(spec, options)其中spec以JSON.stringify(yaml, null, 2) as const形式内嵌自动生成src/schema/openapi/index.ts内容为export * from ./generated因此插件侧只需要import { createOpenApiRouter } from ../schema/openapi即可生成后会自动执行 lint 与 prettier 格式化并按packages/repo-tools/src/lib/openapi/constants.ts中的OPENAPI_IGNORE_FILES清理无用的模板文件。backstage/backend-openapi-utils正是类型化 Router 的运行时核心。根据其 README该包基于oatx库改造而来用于覆写 Express 的值类型。它提供开箱即用的createOpenApiRouter也支持传入validatorOptions做定制若需要在运行时动态修改规范还可以直接使用createValidatedOpenApiRoutertypeof newSpec(newSpec, validatorOptions)。一个常见的坑是当响应content中定义了 charset例如response.content[application/json; charsetutf-8]时响应类型可能会被推导为unknown应避免在 content 键中携带 charset。从规范生成类型化的客户端在当前后端插件目录下运行yarn backstage-repo-tools package schema openapi generate --client-package plugin-client-directory其中plugin-client-directory是一个需要你新建的目录和 npm 包。通用做法是给插件的 common 包新增一个入口即plugins/plugin-name-common/client。同样建议把该命令加入package.json以便复用。生成的客户端会在plugin-client-directory/src/schema/openapi/generated目录下产出DefaultApiClient类以及全部生成类型。客户端生成的前提条件根据 docs/openapi/generate-client.md生成客户端前需要满足两点将 OpenAPI 文件的info.title设置为你的 pluginId例如info: # your pluginId title: catalog找到或新建一个用于承载生成客户端代码的插件包。目前工具不支持生成一个全新插件只会生成客户端文件。在现有 Client 中封装使用以CatalogClient为例将生成的DefaultApiClient作为内部实现细节封装起来 import { DefaultApiClient } from ../schema/openapi/generated; export class CatalogClient implements CatalogApi { private readonly apiClient: DefaultApiClient; constructor(options: { discoveryApi: { getBaseUrl(pluginId: string): Promisestring }; fetchApi?: { fetch: typeof fetch }; }) { this.apiClient new DefaultApiClient(options); } ...具体如何使用这些类型取决于你的类型命名与规范中的 schema 名称一一对应。生成的DefaultApiClient可以直接用于日常 API 调用如果需要更强的定制能力可以在其外层封装一个包装类来调整客户端的口味例如统一错误处理、日志、认证头注入等。生成客户端注意点根据 docs/openapi/generate-client.md不要从src/schema/openapi/generated父目录的子文件夹中导入任何内容所有需要的东西都应从src/schema/openapi/generated/index.ts导出主要包括DefaultApiClient——用于访问你的具体规范对应 API 的客户端各种请求/响应类型——名称与规范中的定义保持一致可从 index 直接导入。从源码看packages/repo-tools/src/commands/package/schema/openapi/generate/client.ts 会使用typescript-backstage-client.yaml模板生成代码随后自动写入父目录index.ts内容为export * from ./generated并执行 lint、prettier 与 import 去重最后清理.openapi-generator-ignore、.gitattributes等临时产物。用测试流量验证规范与实现的一致性在插件的createRouter.test.ts或router.test.ts中加入以下改动 import { wrapServer } from backstage/backend-openapi-utils/testUtils; import type { Server } from node:http; ... describe(createRouter, () { - let app: express.Express; let app: Server; ... - app express().use(router); app await wrapServer(express().use(router));wrapServer会建立一个代理在测试期间捕获所有请求与响应并对照你的 OpenAPI 规范进行校验。任何规范与实际 API 行为之间的不匹配都会以测试失败的形式报告出来。完整示例下面是 docs/openapi/test-case-validation.md 提供的完整测试示例import { wrapServer } from backstage/backend-openapi-utils/testUtils; import express from express; import type { Server } from node:http; import request from supertest; import { createRouter } from ./router; describe(createRouter, () { let app: Server; beforeAll(async () { const router await createRouter(); app await wrapServer(express().use(router)); }); // Bad: the empty object wont satisfy the required properties in the spec, // causing the OpenAPI validation proxy to fail the test. it(should not use an empty mock, async () { const entity: Entity {} as any; app.get(/test, () { return entity; }); const response await request(app).get(/test); expect(response.body).toEqual(entity); }); // Good: all required properties are present, so the response matches the // spec and validation passes. it(should return a valid entity, async () { const entity: Entity { apiVersion: a1, kind: k1, metadata: { name: n1 }, }; app.get(/test, () { return entity; }); const response await request(app).get(/test); expect(response.body).toEqual(entity); }); });这个例子非常直观地展示了测试的价值第一个用例返回空对象{} as any无法满足规范中实体的必填属性校验代理会直接让测试失败第二个用例补齐了所有必填属性校验通过。校验失败时的处理路径当发现校验错误时通常有两种解决方式手工修正规范——通常适用于请求体或响应体发生变化的情形修正测试用例——确保测试返回完整的、填充好的返回值。从测试基础设施看backstage/backend-openapi-utils/testUtils对应的实现位于 packages/backend-openapi-utils/src/testUtils.ts而请求体、响应体、参数的逐项校验逻辑分别实现在 packages/backend-openapi-utils/src/schema/request-body-validation.ts、packages/backend-openapi-utils/src/schema/response-body-validation.ts 与 packages/backend-openapi-utils/src/schema/parameter-validation.ts 中每个模块都配有对应的*.test.ts测试文件可以作为理解校验规则的参考。完整工作流小结一个典型的 Schema-First 开发循环如下在后端插件中创建src/schema/openapi.yaml编写或从 3.0 迁移到OpenAPI 3.1 规范运行yarn backstage-repo-tools package schema openapi validate确认规范结构合法必要时运行repo schema openapi lint检查风格运行yarn backstage-repo-tools package schema openapi generate --server生成类型化 Router并在createRouter.ts中接入运行yarn backstage-repo-tools package schema openapi generate --client-package plugins/plugin-name-common/client生成客户端在CatalogClient之类的高层客户端中封装DefaultApiClient在测试中通过wrapServer包裹测试服务让测试流量反向校验规范与实现的同步性可选借助package schema openapi diff在 CI 中检测规范的破坏性变更——该命令需要oasdiffCLI 与java环境。至此你的 OpenAPI 规范就不再只是一份文档而是贯穿插件后端类型、客户端代码与测试验证全流程的可执行契约。【免费下载链接】backstageBackstage is an open framework for building developer portals项目地址: https://gitcode.com/GitHub_Trending/ba/backstage创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表