规范优先的 API 开发
规范优先这件事真正有价值,不是因为它“更规范”,而是因为它能把 API 的关键决策提前暴露出来。
更具体地说:
很多 API 真正昂贵的问题,不是实现难,而是接口一旦被别人依赖,后面就很难改。
如果 spec 是在代码写完之后补的,那很多高成本错误其实已经定型了。
所以这篇文章不打算再讲“Swagger 可以生成好看的文档”这种入门话术,而是讲一件更现实的事:在一个要长期被依赖的 API 里,spec-first 到底怎么落地才有用。
本文默认使用 OpenAPI 3.1。到 2026 年,这已经是新 API 设计里比较正常的基线了。
什么叫规范优先,什么不叫
规范优先的意思是:
- API 契约先设计
- 先评审
- 先版本化
- 然后实现再去贴这个契约
它不等于:
- 一开始就把未来三年的接口全写完
- spec 写一次永远不改
- 代码生成能代替工程判断
- 前后端和产品经理只靠 YAML 远程扔需求
如果团队把 spec-first 做成一种“文档合规动作”,那它会很快失去生命力,而且是也是自然的结果。
真正用得好的 spec-first,通常会带来这些收益:
- API 设计评审更早发生
- 兼容性问题更早暴露
- 前后端可以更早并行
- mock 和契约测试更容易做实
- 发布和变更记录更清楚
为什么现在建议直接用 OpenAPI 3.1
OpenAPI 3.1 的意义,不是“版本号更新了”,而是它把很多以前 awkward 的地方理顺了。
实用层面的好处主要有:
- 和现代 JSON Schema 更贴近
- schema 表达能力更自然
- 校验行为和生态工具更统一
- 少很多“为了兼容旧规范而拐弯”的写法
如果你们公司因为老系统包袱还在用旧版 OpenAPI,那可以理解。
但新 API 如果没有工具链阻塞,直接用 3.1 会更省心。
spec-first 真正重要的场景
不是所有 API 都一定要把 spec-first 做得很重。
它的价值在这些场景里尤其明显:
- API 有多个消费者
- 前后端要并行开发
- 对外开放给客户、合作方、第三方
- 后端还没完成,前端就要先联调
- 兼容性要求明确存在
- 变更需要可追踪、可评审
如果只是某个临时内部接口,一个服务只给另一个服务自己人用,也不是说 spec-first 没价值,只是收益没那么大。
别一上来就写 schema,先把交互流程想清楚
很多团队做 API 设计时,一个典型问题是:
字段还没想清楚业务语义,就已经开始写 schema 了。
这种顺序很容易把问题做歪:
- 先列一堆对象模型
- 先开始争字段类型
- 最后才发现接口行为本身就没想明白
更靠谱的顺序应该是:
- 先定义业务流程或用户流程
- 再识别资源和操作
- 再定义成功与失败语义
- 最后再收敛 request / response schema
比如,在争论字段名之前,应该先回答这些问题:
- 这是同步接口还是异步接口?
- 创建动作返回最终资源,还是返回一个 operation handle?
- 客户端允许重试吗?
- 幂等性怎么定义?
- 分页过程中数据变化了怎么办?
- 哪些失败是可重试的,哪些不是?
这些问题,比字段到底叫 createdAt 还是 created_at 更影响长期质量。
设计评审应该基于 spec,而不是基于页面截图
规范优先最重要的地方之一,就是它提供了一个可以评审的契约载体。
一次有用的 API 设计评审,至少应该覆盖:
- 接口的目的是什么
- 主要消费者是谁
- 资源命名是否清晰
- method 语义是否一致
- 鉴权模型是什么
- 分页、过滤、排序规则是否稳定
- 错误结构是否统一
- 幂等性是否定义清楚
- 兼容性边界是什么
- 有没有足够真实的请求/响应示例
评审不应该只变成“YAML 能不能过 lint”。
能过 lint 只是说明格式没写坏,不代表设计本身没问题。
真正要评的是:
- 我们到底要求客户端依赖什么?
- 我们是不是正把某些错误设计永久化?
兼容性规则必须写下来,不能靠默契
很多团队嘴上都说“我们很重视 backward compatibility”,但真问一句“什么叫 breaking change”,往往说不清。
一个比较实用的 API 兼容性规则,通常至少要明确这些:
一般算兼容
- 新增可选字段
- 新增新的 endpoint
- 新增可选 query 参数
- 在语义不变前提下适度放宽部分校验
- 某些允许忽略未知值的场景里增加 enum 值
很可能是 breaking change
- 删除字段
- 重命名字段
- 修改字段类型
- 把 optional 改成 required
- 改分页语义
- 改错误结构
- 在没说明的情况下修改默认排序
- 修改鉴权要求
- 结构不变但语义变了
这类规则如果不落文档,最后通常就会变成“大家都觉得自己没改坏”,然后消费者线上出问题。
如果能在 CI 里做兼容性检查更好,但前提还是先把规则说清楚。
示例不是装饰品,而是契约的一部分
很多 spec 技术上完整,但示例写得很敷衍,结果可用性大打折扣。
一个好的 example 作用非常大:
- 能让语义一眼看明白
- 能暴露命名上的牵强
- 能提前看出边界情况有没有漏
- 能让前端、SDK、测试更早启动
- 能让 mock 真正有价值
- 能显著提升自动生成文档的可读性
反过来,差的 example 通常有这些特点:
- 太小
- 太理想化
- 和 schema 自己都对不上
- 明显不是从真实使用场景里长出来的
建议至少给这些场景写 example:
- 正常成功返回
- 空结果
- 参数校验失败
- 鉴权失败
- 分页续页
- 异步任务处理中 / 完成 / 失败
没有示例的 spec,不是不能用,但会明显难用很多。
错误结构值得单独认真设计
很多 API 在 happy path 上写得很认真,一到错误返回就随手拼个 message。
这在生产里是很糟糕的习惯。
因为客户端通常最依赖的,不是成功响应,而是失败时怎么判断下一步。
一个面向生产的错误契约,至少应该定义:
- 统一的顶层错误结构
- 稳定的 machine-readable error code
- 面向人看的 message
- 需要时的字段级错误明细
- trace / correlation id
- 可重试提示(如果场景需要)
客户端应该能明确区分这些情况:
- 请求结构不对
- 未登录
- 无权限
- 资源不存在
- 状态冲突
- 触发限流
- 下游暂时失败
- 异步任务还没完成
不要让消费者去猜字符串含义。
代码生成很好用,但边界一定要守住
很多团队做 spec-first,很快就会走到 codegen。
这一步很有价值,但也很容易遇到问题。
比较适合生成的部分通常是:
- typed client / SDK 骨架
- request / response model
- server interface stub
- 基础校验代码
- mock / docs 相关产物
不太适合直接交给生成器统治的部分通常是:
- 生产级业务逻辑
- 持久化模型
- 权限判断
- 复杂工作流
- 一边手改、一边还想安全重生成的服务端代码
经验上,codegen 最有价值的边界是:
让它负责“重复而契约化”的部分,业务语义和行为判断仍然由工程代码自己承担。
如果团队开始把“生成出来的代码结构”当成服务架构本身,那基本很快会反噬。
Mock server 的价值,是让前后端不再互相卡死
spec-first 最直接的收益之一,就是后端没完全写完之前,前端和消费者也可以先动起来。
一个真正有用的 mock setup,应该能支持这些事情:
- 验证 route 形状
- 验证 auth header
- 验证 request payload
- 验证 response schema
- 提前处理空态、错态、分页态、异步态 UI
但 mock 能不能有用,取决于 spec 是否足够具体。
如果 spec 只写成“200 返回 object”,那 mock server 只是表演,不是基础设施。
契约测试是防止 spec 漂移的关键
spec 如果不对实现做约束,它迟早会漂。
至少要有两类约束意识:
Provider 侧
现在跑着的服务,是否仍然符合这个契约?
Consumer 侧
关键消费者依赖的字段、状态码、错误语义,有没有被悄悄改掉?
这不需要什么主观猜测流程,它只需要团队接受一个事实:
spec 不是展示用的文档,它应该是可执行、可验证的产物。
一旦有多个客户端、多个实现方、或者对外 API,这一点就尤其重要。
Changelog 和发布纪律,比很多人想的更重要
spec-first 只有在“变更也被认真管理”时,价值才能持续。
至少应该记录清楚:
- 改了什么
- 是否兼容
- 消费者是否需要动作
- 如果不兼容,迁移路径是什么
- 从哪个时间点或版本边界开始生效
很多团队的问题不是没 spec,而是 spec 变更太安静。
实现上线了,消费者只能靠线上报错才知道接口已经不一样了。
API 契约之所以稳定,不是因为你用了 OpenAPI。
而是因为你把它当成了一个要发布、要审查、要变更管理的产品表面。
spec-first 最容易失败的几种方式
这几个坑非常常见。
1. spec 变成补文档动作
真正的实现决策在别的地方发生,最后再回来补 YAML。
这不叫 spec-first,这叫事后备案。
2. spec 写得太抽象
没有示例、没有边界、没有清晰错误语义,这种 spec 很难指导实现,也很难指导消费方。
3. 过度迷信代码生成
什么都想生成,最后没有人真正对业务语义负责。
4. 兼容性只存在于口头
大家都说“这不算 breaking”,但没人有明确规则,结果消费者被动遇到问题。
5. spec 不进 CI
没有 lint、没有 diff review、没有兼容性检查、没有契约测试,漂移几乎是必然的。
一个比较实用的落地流程
如果你想把 spec-first 做成真能用的工程流程,通常可以按这个顺序来:
- 先定义资源和交互语义
- 起 OpenAPI 3.1 草案
- 把错误结构和 example 补完整
- 拉产品、后端、消费者一起评审
- 做 lint 和 schema 校验
- 生成 mock 工具链
- 按评审后的契约开始实现
- 加 provider / consumer 契约检查
- 对契约变更做版本和 changelog 管理
这套流程一点也不花哨,但它很有效。
它决定了你的 API 是“有一份文档”,还是“有一份可靠契约”。
最后一句
规范优先真正有价值,不是因为它形式更完整,而是因为它能把高成本错误往前挪。
如果你把 OpenAPI 3.1 当成:
- 设计评审载体
- mock 来源
- 校验目标
- 兼容性边界
- 发布变更面
那 spec-first 很值得做。
如果你只是等服务写完,再导一份文档出来,那你做的不是规范优先,只是把历史补写了一遍。
而历史,往往比草案更难改。