背景
做了 3 年中间件研发,期间给 4 个团队搭过 API 网关、写过服务间调用 SDK。每次 onboarding 新同学,我都会问一句:「咱们的 OpenAPI spec 文件在哪?最近一次更新是什么时候?」
答案通常是:
- 「在 Confluence 上,但……可能有点旧了。」
- 「Swagger UI 能看,但那是注解生成的,字段名和实际返回偶尔对不上。」
- 「没有文档,看代码 + Postman 集合。」
去年有一次真实的坑:下游团队按我们文档写的客户端,把订单金额字段当 int(单位:分)处理,但实际返回的是 string(单位:元,带两位小数)。文档里写的类型和实际响应差了一个量级,线上跑了 3 天才被对账发现。事后复盘,文档是 14 个月前写的,中间改过两次字段类型,spec 文件没跟着改,CI 里也没有任何校验。
这件事让我开始认真想一个问题:我们的 API 契约,到底在"管理"接口,还是只是在"装饰"接口?
常见分歧(我观察到的几种做法)
做法一:契约先行(Contract-First)
先写 .yaml / .json spec → 用 openapi-generator 生成服务端 stub + 客户端 SDK → 各端基于 stub 开发 → CI 里跑 contract test(Dredd / Schemathesis / openapi-diff)。
- 优点:接口变更走 spec diff,下游能提前感知;多语言客户端自动生成。
- 代价:spec 本身成为"第二份代码",维护成本翻倍;小团队容易 spec 和实现"各跑各的"。
做法二:实现先行(Code-First)
写代码 → 用注解(@Schema / @ApiOperation)→ 启动时生成 Swagger/OpenAPI JSON → 够用就行。
- 优点:零额外文件,文档和代码"天然同步"(只要注解没忘写)。
- 代价:注解经常和实际逻辑脱节(比如
@Schema(type="int")但实际返回String);没有 spec 就无法做 contract test;下游拿到的 JSON 里全是实现细节,没有"契约语义"。
做法三:无契约 看代码 + Postman 集合 + 口头约定 + IM 里问人。
- 优点:零维护成本(笑)。
- 代价:人一走,接口就"失传";跨团队协作全靠信任。
做法四(折中):核心接口契约先行,内部工具接口实现先行 对外 / 跨团队 / 跨语言的接口走 spec-first;团队内部 CRUD 接口走注解生成。
- 优点:成本可控。
- 代价:边界模糊——"什么算核心接口"本身就需要定义,而且定义之后又没人更新。
我的倾向与理由
我倾向于分层的契约策略,核心原则是「契约是代码,不是文档」:
-
跨团队 / 对外接口:必须 spec-first。spec 文件进 git,走 code review,CI 里做三件事:
openapi-diff对比上一版,breaking change 直接 fail pipeline;- Schemathesis 对 running 实例做 property-based contract test(随机生成合法请求,校验响应 schema);
- 客户端 SDK 从 spec 生成,下游升级 SDK 就是升级契约。
-
团队内部接口:Code-first + 注解可以,但必须加一道"文档新鲜度"检测——CI 里对比最近 N 天 git diff 中涉及 Controller / 响应 DTO 的文件,如果 spec / Swagger JSON 没有对应更新,打 warning(不 block,但进周报)。
-
不管哪种方式,"谁负责维护契约"必须落到具体的人头上,写进团队 wiki 的 RACI 表里。没有 owner 的契约 = 没有契约。
-
一个避坑清单(我踩过的):
- 别信
@Schema(type="int")的注解——它只是"声明意图",不校验实际返回值。CI 里一定要用 Schemathesis 或 Postman Collection Runner 跑真实响应。 - 字段类型变更(int→string、enum 加值)是最高危的 breaking change,比"删字段"更容易被忽略,因为下游可能不报错、只是算错。
- Postman 集合不是契约,它是"操作手册"。把它当契约用的团队,出事故的概率和"无契约"差不多。
- 别信
想听大家
- 你们团队目前是哪种做法?实际跑下来,契约文件的更新频率和实现变更频率的"同步率"大概多少?
- 有没有"文档和实现脱节"导致线上事故的实战经验?(想听具体故事,越具体越好)
- 小团队(5 人以下)值得上 contract-first 吗?还是说 Code-First + 一个 Postman 集合就够用了?
- 你们团队有没有"契约 owner"这个角色?如果有,是怎么推动的?如果没有,接口变更的沟通靠什么?
欢迎拍砖。