桃源乡/ notes
← 返回文章列表

让 OpenAPI v3 文档从代码里长出来

手写 YAML 和实现对不上,是文档腐化的开始。把 schema 变成唯一来源,文档就只是构建产物。

OpenAPIAPI 设计CI

接手一个接口项目时,最先过期的通常不是代码,是 API 文档。字段加了、枚举改了、错误码多了一个,代码里改完就发,YAML 躺在仓库角落没人动。等到对接方拿着旧文档来问,才发现两边已经差了三个版本。

单一来源

根治办法只有一个:不让文档拥有任何独立信息。请求/响应的类型定义在代码里写一次,构建时导出 JSON Schema,再由它生成 OpenAPI 的 components.schemas。文档变成产物,不进人工编辑流程。

路径部分可以保留手写,但只允许引用,不允许重复定义:

paths:
  /v1/tasks/{task_id}:
    get:
      operationId: getTask
      tags: [Tasks]
      parameters:
        - $ref: '#/components/parameters/TaskId'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Task'
              examples:
                default:
                  $ref: '#/components/examples/TaskExample'

几个值得写进规范的约束

  • operationId 全局唯一且稳定。 客户端 SDK 靠它生成方法名,改一次就是一次破坏性变更。
  • examples 必填。 只有 schema 没有示例,对接方依然要靠猜。示例最好来自真实测试夹具,别手写。
  • 错误响应用统一的 Error schema。 别每个接口各写一套 code/message,字段名字差一个字母,客户端就得写特例。
  • 用 tags 分组,别用路径前缀分组。 路径会重构,语义分组不会。

CI 里加两道闸

# 1. 规范校验
npx @redocly/cli lint openapi.yaml
# 2. 产物一致性:重新生成后与仓库内的文件比对
make gen-openapi && git diff --exit-code -- openapi.yaml

第二道最关键。lint 只能保证格式合法,保证不了「文档等于代码」。只要生成结果和提交进来的文件有 diff 就拦住,腐化就无从发生。

踩过的一个小坑

OpenAPI 3.0 里 $ref 旁边写的兄弟字段会被静默忽略,不报错。曾经给一个引用的参数补了 description,怎么都不生效,查了半天才发现要写进被引用对象本身,或者升级到 3.1 的语义。这类「不报错但没生效」的问题最费时间。

文档不该靠人记得更新,该靠流水线不许它落后。