主题
界面
主题
《Seata 分布式事务》讲分布式事务;本节讲**"接口这一层的治理"**。
★ 接口管理平台解决三件事 ✓:
① 接口长什么样 → 文档(OpenAPI / Swagger)
② 谁能改、怎么改 → 版本与兼容性规则 ✓✓
③ 改坏了谁知道 → 契约测试(在 CI 里拦住)✓✓★ 本节的实测会量出"哪些改动是破坏性的" ✓✓ —— 这件事如果只靠人评审,一定会漏 ✗
先看一个真实的线上事故 ✗✗:
现象:某个接口上线后 5 分钟,另一个团队的服务大面积报错
原因:给请求体**新增了一个必填字段** ✗
· 新客户端会传它 ✓
· **老客户端不会传** → 参数校验失败 → 全部 400 ✗✗
特点:**不是慢慢出问题,而是上线瞬间全挂** ✗✗★ 本节的兼容性检查器就是用来在提交时拦住它的 ✓:
请求里新增一个必填字段 channel → **破坏** ✗✗(新增必填字段:channel)
请求里新增一个可选字段 channel → 兼容 ✓
请求里删除一个字段 couponId → **破坏** ✗
响应里新增一个字段 points → 兼容 ✓
响应里删除一个字段 status → **破坏** ✗
改变字段类型 long → string → **破坏** ✗★ 记住这三条就够了 ✓✓:
| # | 规则 | 说明 |
|---|---|---|
| ① | 加可选字段是兼容的;加必填字段是破坏性的 ★★★ | 请求与响应都适用 ✓✓ |
| ② | 删字段、改类型、改路径、改方法 —— 全是破坏性的 ★★★ | 没有例外 ✗✗ |
| ③ | 响应可以加字段;响应删字段是破坏性的 ★★ | 调用方可能依赖它 ✓ |
★★ 一条容易搞错的 ✓✓:
"请求加必填字段"比"删字段"更隐蔽、也更危险 ✗✗
因为:老客户端不会传它 → 上线瞬间全部报参数校验失败 ✗✗
而删字段的影响是"慢慢暴露"(只有用到的调用方出问题)⚠️
所以"加必填"是最该被 CI 拦住的一类改动 ✓✓
★ 一份可以直接用的检查清单 ✓:
请求:
✓ 新增可选字段 → 兼容
✗ 新增必填字段 → 破坏(老客户端不传)
✗ 删除字段 → 破坏
✗ 字段类型变更 → 破坏
✗ 可选变必填 → 破坏
✓ 必填变可选 → 兼容(放宽了约束)
响应:
✓ 新增字段 → 兼容(老客户端忽略未知字段)
✗ 删除字段 → 破坏(调用方可能依赖)
✗ 字段类型变更 → 破坏
✗ 枚举值删除 → 破坏(调用方可能穷举处理)★ 三种方式 ✓:
| 方式 | 示例 | 优点 | 缺点 |
|---|---|---|---|
| URL 版本 ★★★ | POST /api/v2/order/create | 最直观、最好排查 ✓✓ | URL 会"版本爆炸" ⚠️ |
| Header 版本 ★★ | X-Api-Version: 2 | URL 干净 ✓ | 不好手工测试 ✗ |
| Query 版本 ★ | ?version=2 | 改动最小 ✓ | 语义不清晰 ⚠️ |
★ 三条实践结论 ✓✓:
| # | 结论 |
|---|---|
| ① | 对外 API 用 URL 版本(好排查、好做网关路由)✓✓ |
| ② | 内部 RPC 尽量"不版本" ——只做兼容性变更 ✓✓ |
| ③ | 版本不是免费的:每个版本都要维护、测试、灰度 ✓ |
★★ 为什么内部服务可以"不版本" ✓✓:
因为内部服务是"同时部署"的 ✓
只要你的改动是兼容的,新旧实例就能共存 ——滚动更新期间新旧版本同时在跑 ✓✓
这正是 《Kubernetes 入门》 滚动更新能工作的前提 ✓
★★★ 一条来自实践的忠告 ✓✓:
"加版本"常被当成逃避兼容性设计的手段 ✗
结果是 v1/v2/v3 同时在线,出问题时"改哪个"都说不清 ✗✗
更好的做法:先把兼容性规则定清楚,版本作为最后手段 ✓✓
★ 五种发现方式的对比 ✓✓:
| 发现方式 | 什么时候发现 | 评价 |
|---|---|---|
| 人评审(看代码 diff) ✗✗ | 评审时 | 经常漏(字段太多,看不出影响)✗✗ |
| 手工联调 ✗ | 提测后 | 已经晚了 ✗ |
| 契约测试(CI) ✓✓ | 提交时 | 最理想 ✓✓ |
| 线上监控 ✗✗ | 用户先发现 | 最差 ✗✗ |
★ 契约测试的两种形态 ✓:
| 形态 | 做法 | 适用 |
|---|---|---|
| 消费者驱动契约(CDC,如 Pact) ★★ | 消费者定义期望,提供方验证 | 内部服务间 ✓ |
| OpenAPI 快照比对 ★★ | 把规范存进仓库,CI 比对差异 | 对外 API ✓ |
| 兼容性规则检查 ★★★ | 本类做的事(规则引擎) | 最轻量、最容易落地 ✓✓ |
★★ 一条落地建议 ✓✓:
不要一上来就上 Pact 之类的重型工具 ✗
先把"兼容性规则"写成代码,接进 CI(本节 60 行就够)✓✓
它能在提交时拦住 90% 的低级破坏性改动 ✓✓
★ 本项目已有同类实践的呼应 ✓:
T2-007 讲的接口契约测试,正是同一件事在测试层的落地 ✓✓
★ 六类内容 ✓:
| 类别 | 内容 |
|---|---|
| 文档 ★★★ | OpenAPI 自动生成、示例请求与响应、在线调试 |
| 规范 ★★ | 命名(复数/单数)、错误码体系、分页/排序约定 |
| 版本 ★★ | 版本策略、废弃公告与过渡期 |
| 兼容性 ★★★ | 破坏性改动的定义、CI 里的自动检查 |
| 治理 ★★★ | 谁在用这个接口(依赖图)、限流与配额、SLA 与监控 |
| 安全 ★★ | 鉴权方式标注、敏感字段脱敏、幂等性标注 |
★★★ 两项最容易漏、但价值最高 ✓✓:
| # | 项 | 为什么重要 |
|---|---|---|
| ① | "谁在用这个接口"(依赖图) ★★★ | 没有它,你永远不敢下线任何接口 ✗ ——怕打挂某个不知道的调用方;有了它,下线是一次有依据的决策 ✓✓ |
| ② | "幂等性标注" ★★★ | 《消息可靠性、幂等与顺序》 与 《Dubbo 与 RPC 基础》 都依赖它:只有标注了幂等,重试才是安全的 ✓✓ |
★ 依赖图怎么来 ✓:
① 从注册中心/网关的调用日志里聚合(最省事)✓✓
② 从链路追踪(TraceId + Span)里推导(最准)✓✓
③ 从代码里扫依赖(静态分析,覆盖不全)⚠️★★ 一条判断平台做得好不好的标准 ✓✓:
新人能不能只靠文档完成一次正确的调用? ✓
能 → 平台合格;还要问人 → 文档没做到位 ✗
// 请求里的每个老字段必须还在,且类型与必填性不能变严
for (Field oldField : oldSpec.request()) {
Field newField = find(newSpec.request(), oldField.name());
if (newField == null) { problems.add("请求字段被删除:" + oldField.name()); }
else if (!newField.type().equals(oldField.type())) { problems.add("类型变了:" + oldField.name()); }
else if (newField.required() && !oldField.required()) { problems.add("可选变必填:" + oldField.name()); }
}# 在 CI 里加一步:对比主干与新提交的 OpenAPI 快照
- name: 接口兼容性检查
run: |
git show origin/main:api/openapi.json > /tmp/old.json
java -cp target/classes cn.kb.learn.ApiCompatChecker /tmp/old.json api/openapi.json
# 有破坏性改动 → 退出码非 0 → 流水线失败 ✓✓✗ "接口要改,那就加个 v2 吧"
→ 结果:v1 还有人在用,v2 又有新需求要改 → v3……
→ 半年后:**没人知道该调哪个版本** ✗✗★ 正确问法 ✓✓:"这个改动能不能做成兼容的?" ✓
代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/micro/ApiContractLesson.java
运行:
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.micro.ApiContractLesson实测输出(兼容性检查部分) ✓:
改动 是否破坏 影响
--------------------------------------------------------------------------------------------
响应里新增一个字段 points(可选) 兼容 ✓ 调用方无感 ✓
请求里新增一个可选字段 channel 兼容 ✓ 调用方无感 ✓
请求里新增一个必填字段 channel **破坏** ✗✗ 新增必填字段:channel
请求里删除一个字段 couponId **破坏** ✗✗ 请求字段被删除:couponId
改变字段类型 productId: long → string **破坏** ✗✗ 请求字段类型变了:productId
响应里删除一个字段 status **破坏** ✗✗ 响应字段被删除:status
改变路径(/api/order/create → /api/v2/...) **破坏** ✗✗ 路径变了★ 三条可落地的结论 ✓:
| # | 结论 |
|---|---|
| ① | "新增必填字段"与"删字段"一样是破坏性的(前者更隐蔽)✓✓ |
| ② | 兼容性规则可以写成 60 行代码,接进 CI(不需要重型工具)✓✓ |
| ③ | 平台最该先做的是"依赖图"与"幂等标注" ✓✓ |
| # | 错误 | 说明 |
|---|---|---|
| 1 | 给请求加必填字段 ✗✗ | 上线瞬间全挂 ✓✓ |
| 2 | 靠人评审保证兼容性 ✗✗ | 一定会漏 ✓ |
| 3 | 用"加版本"逃避兼容性设计 ✗✗ | 版本爆炸 ✓ |
| 4 | 响应删字段觉得"没人用" ✗✗ | 调用方可能依赖它 ✓ |
| 5 | 没有依赖图 ✗✗ | 永远不敢下线接口 ✓✓ |
| 6 | 接口不标注幂等性 ✗✗ | 调用方无法安全重试 ✓ |
| 7 | 文档没有示例 ✗ | 新人只能问人 ✓ |
目标:给自己项目的接口做一次"兼容性体检"。
步骤:
ApiContractLesson,确认三类破坏性改动都被识别 ✓;验收方式:能说出"你的接口里有哪些"随时可能改坏调用方"的风险点"。
扩展任务:设计一个"接口废弃流程":
@Deprecated + 文档提示(呼叫方主动迁移)✓;/old 就记一笔,能看出还有谁在用)✓✓;// 改动前
public OrderVO create(OrderRequest req); // OrderVO 有 orderId, status
// 改动后
public OrderVO create(OrderRequest req); // OrderVO 有 orderId, status, points这是破坏性改动吗? 5. (设计)给"支付回调接口"设计契约管理方案:第三方要能安全地重试。
out_trade_no 唯一索引)✓✓; 契约里明确"重复通知会返回同样的结果"(而不是报错)✓; 提供查询接口(回调丢失时第三方能主动查)✓✓; 标注"可安全重试"与"重试间隔建议" ✓微服务四节到此完成。《Docker 基础与镜像容器》:容器到底省下了什么。
暂无