让 OpenAPI v3 文档从代码里长出来
手写 YAML 和实现对不上,是文档腐化的开始。把 schema 变成唯一来源,文档就只是构建产物。
接手一个接口项目时,最先过期的通常不是代码,是 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 没有示例,对接方依然要靠猜。示例最好来自真实测试夹具,别手写。- 错误响应用统一的
Errorschema。 别每个接口各写一套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 的语义。这类「不报错但没生效」的问题最费时间。
文档不该靠人记得更新,该靠流水线不许它落后。