Skip to content

接口管理平台 ​

S4-024选修进阶约 45 分钟JDK 21企业开发进阶 · 微服务

1. 本节定位 ​

《Seata 分布式事务》讲分布式事务;本节讲**"接口这一层的治理"**。

★ 接口管理平台解决三件事 ✓:

text
① 接口长什么样    → 文档(OpenAPI / Swagger)
② 谁能改、怎么改  → 版本与兼容性规则 ✓✓
③ 改坏了谁知道    → 契约测试(在 CI 里拦住)✓✓

★ 本节的实测会量出"哪些改动是破坏性的" ✓✓ —— 这件事如果只靠人评审,一定会漏 ✗

2. 学习目标 ​

  • 能背出兼容性的三条铁律 ★★★;
  • 知道"请求里加必填字段"比"删字段"更隐蔽 ★★★;
  • 能对比三种版本管理方式,并知道什么时候不该加版本 ★★★;
  • 知道契约测试的价值是"把发现时机从线上提前到 CI" ★★★;
  • 知道"谁在用这个接口"(依赖图)是最该先做的事 ★★★;
  • 知道"幂等性标注"为什么是重试的前提 ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看一个真实的线上事故 ✗✗:

text
现象:某个接口上线后 5 分钟,另一个团队的服务大面积报错
原因:给请求体**新增了一个必填字段** ✗
      · 新客户端会传它 ✓
      · **老客户端不会传** → 参数校验失败 → 全部 400 ✗✗
特点:**不是慢慢出问题,而是上线瞬间全挂** ✗✗

★ 本节的兼容性检查器就是用来在提交时拦住它的 ✓:

text
  请求里新增一个必填字段 channel    → **破坏** ✗✗(新增必填字段:channel)
  请求里新增一个可选字段 channel    → 兼容 ✓
  请求里删除一个字段 couponId       → **破坏** ✗
  响应里新增一个字段 points         → 兼容 ✓
  响应里删除一个字段 status         → **破坏** ✗
  改变字段类型 long → string        → **破坏** ✗

5. 核心概念 ​

5.1 兼容性三条铁律 ★★★ ​

★ 记住这三条就够了 ✓✓:

#规则说明
①加可选字段是兼容的;加必填字段是破坏性的 ★★★请求与响应都适用 ✓✓
②删字段、改类型、改路径、改方法 —— 全是破坏性的 ★★★没有例外 ✗✗
③响应可以加字段;响应删字段是破坏性的 ★★调用方可能依赖它 ✓

★★ 一条容易搞错的 ✓✓:

"请求加必填字段"比"删字段"更隐蔽、也更危险 ✗✗

因为:老客户端不会传它 → 上线瞬间全部报参数校验失败 ✗✗

而删字段的影响是"慢慢暴露"(只有用到的调用方出问题)⚠️

所以"加必填"是最该被 CI 拦住的一类改动 ✓✓

★ 一份可以直接用的检查清单 ✓:

text
请求:
  ✓ 新增可选字段          → 兼容
  ✗ 新增必填字段          → 破坏(老客户端不传)
  ✗ 删除字段              → 破坏
  ✗ 字段类型变更          → 破坏
  ✗ 可选变必填            → 破坏
  ✓ 必填变可选            → 兼容(放宽了约束)

响应:
  ✓ 新增字段              → 兼容(老客户端忽略未知字段)
  ✗ 删除字段              → 破坏(调用方可能依赖)
  ✗ 字段类型变更          → 破坏
  ✗ 枚举值删除            → 破坏(调用方可能穷举处理)

5.2 版本管理:三种方式与"能不加就不加" ★★★ ​

★ 三种方式 ✓:

方式示例优点缺点
URL 版本 ★★★POST /api/v2/order/create最直观、最好排查 ✓✓URL 会"版本爆炸" ⚠️
Header 版本 ★★X-Api-Version: 2URL 干净 ✓不好手工测试 ✗
Query 版本 ★?version=2改动最小 ✓语义不清晰 ⚠️

★ 三条实践结论 ✓✓:

#结论
①对外 API 用 URL 版本(好排查、好做网关路由)✓✓
②内部 RPC 尽量"不版本" ——只做兼容性变更 ✓✓
③版本不是免费的:每个版本都要维护、测试、灰度 ✓

★★ 为什么内部服务可以"不版本" ✓✓:

因为内部服务是"同时部署"的 ✓

只要你的改动是兼容的,新旧实例就能共存 ——滚动更新期间新旧版本同时在跑 ✓✓

这正是 《Kubernetes 入门》 滚动更新能工作的前提 ✓

★★★ 一条来自实践的忠告 ✓✓:

"加版本"常被当成逃避兼容性设计的手段 ✗

结果是 v1/v2/v3 同时在线,出问题时"改哪个"都说不清 ✗✗

更好的做法:先把兼容性规则定清楚,版本作为最后手段 ✓✓

5.3 契约测试:把"改坏了"提前到 CI ★★★ ​

★ 五种发现方式的对比 ✓✓:

发现方式什么时候发现评价
人评审(看代码 diff) ✗✗评审时经常漏(字段太多,看不出影响)✗✗
手工联调 ✗提测后已经晚了 ✗
契约测试(CI) ✓✓提交时最理想 ✓✓
线上监控 ✗✗用户先发现最差 ✗✗

★ 契约测试的两种形态 ✓:

形态做法适用
消费者驱动契约(CDC,如 Pact) ★★消费者定义期望,提供方验证内部服务间 ✓
OpenAPI 快照比对 ★★把规范存进仓库,CI 比对差异对外 API ✓
兼容性规则检查 ★★★本类做的事(规则引擎)最轻量、最容易落地 ✓✓

★★ 一条落地建议 ✓✓:

不要一上来就上 Pact 之类的重型工具 ✗

先把"兼容性规则"写成代码,接进 CI(本节 60 行就够)✓✓

它能在提交时拦住 90% 的低级破坏性改动 ✓✓

★ 本项目已有同类实践的呼应 ✓:

T2-007 讲的接口契约测试,正是同一件事在测试层的落地 ✓✓

5.4 接口管理平台该管什么 ★★ ​

★ 六类内容 ✓:

类别内容
文档 ★★★OpenAPI 自动生成、示例请求与响应、在线调试
规范 ★★命名(复数/单数)、错误码体系、分页/排序约定
版本 ★★版本策略、废弃公告与过渡期
兼容性 ★★★破坏性改动的定义、CI 里的自动检查
治理 ★★★谁在用这个接口(依赖图)、限流与配额、SLA 与监控
安全 ★★鉴权方式标注、敏感字段脱敏、幂等性标注

★★★ 两项最容易漏、但价值最高 ✓✓:

#项为什么重要
①"谁在用这个接口"(依赖图) ★★★没有它,你永远不敢下线任何接口 ✗ ——怕打挂某个不知道的调用方;有了它,下线是一次有依据的决策 ✓✓
②"幂等性标注" ★★★《消息可靠性、幂等与顺序》 与 《Dubbo 与 RPC 基础》 都依赖它:只有标注了幂等,重试才是安全的 ✓✓

★ 依赖图怎么来 ✓:

text
① 从注册中心/网关的调用日志里聚合(最省事)✓✓
② 从链路追踪(TraceId + Span)里推导(最准)✓✓
③ 从代码里扫依赖(静态分析,覆盖不全)⚠️

★★ 一条判断平台做得好不好的标准 ✓✓:

新人能不能只靠文档完成一次正确的调用? ✓

能 → 平台合格;还要问人 → 文档没做到位 ✗

6. 递进示例 ​

6.1 最小示例:一条兼容性规则(10 行) ​

java
// 请求里的每个老字段必须还在,且类型与必填性不能变严
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()); }
}

6.2 业务示例:CI 里的契约门禁 ​

yaml
# 在 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 → 流水线失败 ✓✓

6.3 反例:用"加版本"逃避兼容性 ​

text
✗ "接口要改,那就加个 v2 吧"
→ 结果:v1 还有人在用,v2 又有新需求要改 → v3……
→ 半年后:**没人知道该调哪个版本** ✗✗

★ 正确问法 ✓✓:"这个改动能不能做成兼容的?" ✓

7. 完整可运行代码与实测 ​

代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/micro/ApiContractLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.micro.ApiContractLesson

实测输出(兼容性检查部分) ✓:

text
  改动                                          是否破坏      影响
  --------------------------------------------------------------------------------------------
  响应里新增一个字段 points(可选)                兼容 ✓      调用方无感 ✓
  请求里新增一个可选字段 channel                   兼容 ✓      调用方无感 ✓
  请求里新增一个必填字段 channel                   **破坏** ✗✗  新增必填字段:channel
  请求里删除一个字段 couponId                     **破坏** ✗✗  请求字段被删除:couponId
  改变字段类型 productId: long → string          **破坏** ✗✗  请求字段类型变了:productId
  响应里删除一个字段 status                       **破坏** ✗✗  响应字段被删除:status
  改变路径(/api/order/create → /api/v2/...)    **破坏** ✗✗  路径变了

★ 三条可落地的结论 ✓:

#结论
①"新增必填字段"与"删字段"一样是破坏性的(前者更隐蔽)✓✓
②兼容性规则可以写成 60 行代码,接进 CI(不需要重型工具)✓✓
③平台最该先做的是"依赖图"与"幂等标注" ✓✓

8. Debug 观察点 ​

  1. 把你的接口文档导成 OpenAPI,对比主干与本地的 diff ✗✓(很多人第一次做会发现"居然改了这么多");
  2. 找出所有"响应字段被删除"的历史提交:看有没有人报过错 ✓;
  3. 给一个接口加必填字段并上线(测试环境):观察老客户端的失败率 ✗✗;
  4. 统计"每个接口有几个调用方":孤立无调用方的可以下线 ✓;
  5. 检查有多少接口标注了幂等性:没标的接口不能重试 ✗。

9. 常见错误 ​

#错误说明
1给请求加必填字段 ✗✗上线瞬间全挂 ✓✓
2靠人评审保证兼容性 ✗✗一定会漏 ✓
3用"加版本"逃避兼容性设计 ✗✗版本爆炸 ✓
4响应删字段觉得"没人用" ✗✗调用方可能依赖它 ✓
5没有依赖图 ✗✗永远不敢下线接口 ✓✓
6接口不标注幂等性 ✗✗调用方无法安全重试 ✓
7文档没有示例 ✗新人只能问人 ✓

10. 动手实验 ​

目标:给自己项目的接口做一次"兼容性体检"。

步骤:

  1. 跑 ApiContractLesson,确认三类破坏性改动都被识别 ✓;
  2. 把兼容性检查接进你的 CI(用本节的 60 行)★★★;
  3. 列出你系统里所有对外接口,标出"有版本 / 无版本" ✓;
  4. 给每个接口补上"幂等性"标注 ★★★;
  5. 统计"一个接口有几个调用方",找出可以下线的 ✓。

验收方式:能说出"你的接口里有哪些"随时可能改坏调用方"的风险点"。

扩展任务:设计一个"接口废弃流程":

  • 阶段一:标记 @Deprecated + 文档提示(呼叫方主动迁移)✓;
  • 阶段二:加告警(调用 /old 就记一笔,能看出还有谁在用)✓✓;
  • 阶段三:设下线日期并通知 ✓;
  • 说明:为什么"直接下线"是最后手段(提示:依赖图不全时,你不知道会打挂谁)★★

11. 练习题 ​

  1. 判断:给请求体新增一个可选字段是破坏性改动。(对 / 错)
  2. 填空:兼容性三条铁律是 、、______。
  3. 简答:为什么"请求加必填字段"比"删字段"更危险?
  4. (判断)
java
// 改动前
public OrderVO create(OrderRequest req);     // OrderVO 有 orderId, status

// 改动后
public OrderVO create(OrderRequest req);     // OrderVO 有 orderId, status, points

这是破坏性改动吗? 5. (设计)给"支付回调接口"设计契约管理方案:第三方要能安全地重试。

参考答案(要点)
  1. 错。加可选字段是兼容的 ✓ ——加必填字段才是破坏性的 ✓★
  2. 加可选兼容、加必填破坏;删字段/改类型/改路径/改方法都是破坏性的; 响应可以加字段,删字段是破坏性的 ✓
  3. 因为老客户端不会传它 ✗ ——上线瞬间所有老客户端都失败(而不是慢慢暴露)✗✗★
  4. 不是。响应新增字段是兼容的 ✓ —— 但要注意:如果调用方用了"严格的 JSON 反序列化(未知字段报错)",那就会出问题 ⚠️ —— 所以提供方要提前说明"响应可能新增字段" ✓✓
  5. 要点:必须幂等(用 out_trade_no 唯一索引)✓✓; 契约里明确"重复通知会返回同样的结果"(而不是报错)✓; 提供查询接口(回调丢失时第三方能主动查)✓✓; 标注"可安全重试"与"重试间隔建议" ✓

12. 掌握标准 ​

  • [ ] 能背出兼容性三条铁律 ★★★;
  • [ ] 知道"加必填字段"是最危险的一类改动 ★★★;
  • [ ] 能对比三种版本方式,并知道内部服务尽量不版本 ★★★;
  • [ ] 知道契约测试的价值是把发现时机提前到 CI ★★★;
  • [ ] 知道"依赖图"与"幂等标注"是平台最该先做的两件事 ★★★;
  • [ ] 能用几十行代码实现兼容性检查并接进 CI ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

微服务四节到此完成。《Docker 基础与镜像容器》:容器到底省下了什么。

后续知识

暂无

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引