Skip to content

API 契约与前后端联调

前后端联调的痛苦,几乎都来自同一件事:没有契约。后端凭感觉改了个字段名,前端不知道;前端按口头描述写的逻辑,后端实际返回的结构对不上;联调那天才发现两边理解完全不同,互相等、互相改、互相怨。API 契约要解决的就是这个——让接口的形状成为一份双方都认可、可校验、可版本化的协议,而不是聊天记录里的一句话。

为什么需要契约

没有契约时,接口的定义散落在聊天记录、文档、代码三处,而且彼此不一致:

text
口头约定:"返回用户列表,每条有 id 和 name"
后端实际:返回 { userId, userName, ... }
前端预期:[{ id, name }]
联调当天:互相阻塞,谁也动不了

契约的价值在于单一事实来源:接口长什么样、字段叫什么、什么类型、什么必填,都由一份明确的描述决定,前后端都以此为准。

没有契约有契约
接口定义在聊天记录里一份机器可读的描述文件
改了字段没人知道改动触发通知和影响分析
联调当天才发现对不上各自按契约开发,提前对齐
出问题互相甩锅以契约为准,对错明确

OpenAPI 与类型共享

OpenAPI(前身 Swagger)是事实标准的 REST API 描述格式。一份 OpenAPI 文件既描述了接口,又能直接派生出多种产物:

text
OpenAPI 定义(openapi.yaml)
  → 后端:生成路由骨架、请求校验、mock
  → 前端:生成 TypeScript 类型和请求客户端
  → 文档:自动渲染的 API 文档
  → 测试:契约一致性校验

最大的收益是类型从契约自动生成,而不是前端手写一遍、后端手写一遍。手写的两份类型迟早会不一致,自动生成保证它们同源。

ts
// 从 OpenAPI 生成的类型,永远和后端一致
type Order = {
  id: string
  amount: number
  status: 'pending' | 'paid' | 'shipped'
}

Mock 与并行开发

契约确定后,前后端不用再互相等。后端还没实现,前端可以基于契约 mock 数据先开发:

text
1. 双方一起定契约(OpenAPI)
2. 前端用 mock server 按契约返回假数据,开发 UI
3. 后端按契约实现接口
4. 联调时前端把 mock 切换成真实接口

mock 要基于契约而不是前端自己瞎编——否则 mock 和真实接口又是两套。常用的做法是用专门的 mock 工具直接读 OpenAPI 文件,返回符合 schema 的假数据。

变更通知:契约是会改的

契约不是一次定死,它会演进。关键是要让契约的变更可见、可协商,而不是悄悄改:

变更类型影响处理
新增可选字段非破坏性直接加,通知即可
新增接口非破坏性加,通知
改字段名/类型破坏性必须协商,前端同意
删除字段破坏性必须走版本协商或废弃周期

破坏性变更最危险。一个看似无害的改动(userId 改成 id),能让前端整个页面崩掉。规则:任何破坏性变更必须走评审,并在 CI 里用契约校验工具拦截,而不是靠人记得。

版本协商

当改动确实是破坏性的,靠版本协商来过渡:

text
策略一:URL 版本
  /api/v1/orders  →  /api/v2/orders
  v1 保持可用,新功能进 v2,逐步迁移

策略二:header 版本 / 内容协商
  Accept: application/vnd.myapp.v2+json

不管哪种策略,核心是旧版本要有明确的废弃周期,给消费者时间迁移,而不是改完就删。同时监控旧版本的调用量,等它趋零再下线。

文档即契约

最好的 API 文档就是契约本身——OpenAPI 文件自动渲染出的文档,永远和实现一致。手写的 wiki 文档最大的问题是它会过期,没人维护,最后没人信。

一个简单的判断标准:如果文档和代码不一致,应该是代码错了(没按契约实现),而不是文档过期了。让契约成为唯一事实,两边都向它对齐。

实现层面,可以在 CI 里加一步契约校验:拿真实接口的响应去匹配 OpenAPI schema,不匹配就失败。这样后端偷偷改了返回结构,CI 会直接报红。

前后端协作流程

把契约驱动串成一条流程:

text
1. 需求评审后,前后端一起定 API 契约(OpenAPI)
2. 契约进版本控制,评审后合并
3. 前端基于契约 mock 开发,后端基于契约实现
4. CI 校验实现与契约一致
5. 联调时切换 mock → 真实接口
6. 后续变更走契约评审 + 变更通知

这套流程把"联调当天的互相等待"前置成了"早期的契约对齐"。联调成本越早付出越便宜,越晚越贵。

务实的检查清单

  • 是否有一份机器可读的 API 契约作为单一事实来源?
  • 前后端的类型是否从契约自动生成,而不是各自手写?
  • 前端是否能基于契约 mock 并行开发,不用等后端?
  • 破坏性变更是否走评审和通知,而不是悄悄改?
  • CI 是否校验实现与契约一致,防止文档和代码漂移?
  • 旧版本接口是否有废弃周期和调用量监控,而不是改完就删?

为复用而记录,为理解而整理。