3182 字
约 10 分钟
15
S18-A17 · 接口版本的成本:什么时候该开新版本

为了加一个字段,有人提议升 v2。这个提议几乎不需要论证——它听起来"更规范"。但它实际提出的是:从此有两套接口要测、要监控、要写文档、要有人值班,且没人知道第二套何时能关。版本不是能力的证明,是一份长期维护义务。该不该开新版本,等于判断这份义务值不值。


1. 版本是负债,不是能力

收益是"这一次不用兼容",一次性的;成本是把状态空间从一份变成两份,按存活期累积。

项 开新版本 兼容扩展
本次改造自由度 高 低,受旧结构约束
新增维护面 测试、监控、文档、值班各一份 一份
退出成本 一次弃用战役 无
适用时间尺度 一次性重构 长期演进

结论不是"尽量别开版本",而是"开版本要说清关闭条件"。没有关闭条件就是永久支持。

2. 什么算破坏性变更

判断破坏性不看改动形式,看旧客户端升级后的行为。第四类最隐蔽。

类型 例子 旧客户端表现
字段删除 去掉 discountAmount 解析失败或取默认值,金额算错
类型变化 amount 从字符串变数字 强类型反序列化抛错
必填化 原可选,现缺失即 400 请求被拒且看不出原因
语义变化 单位元→分、时区本地→UTC、空值"无"→"零" 一切照常,数字全错

它不触发异常,错误只在业务结果里出现,常在几周后才被发现。凡涉及单位、币种、时区、精度、空值语义、排序稳定性、分页游标含义的改动,都按破坏性处理。

// 契约差异检查:结构与语义分两路审
oasdiff breaking BASE.yaml REV.yaml   // 结构性差异,工具可判
─── 语义差异工具判不出,只能人工 diff 字段含义
─── 提交前搜索"单位 / 时区 / 空值",命中的字段必须确认

3. 兼容扩展的四种手段

多数需求不必升版本。四种手段按成本递增,能停在前两步就别往后走。

手段 做法 前提 失效场景
加可选字段 字段带合理默认 客户端忽略未知字段 强校验字段全集
允许未知字段 两端宽容解析 契约写明向后兼容 生成式客户端严格绑定
宽松解析 未知枚举降 UNKNOWN 有兜底分支 switch 全覆盖
双写期 新旧字段同时返回 有摘除日期 无人收尾变永久
// 宽容解析:未知枚举与缺失字段都要有兜底(Jackson 口径)
@JsonIgnoreProperties(ignoreUnknown = true)
public enum Channel {
    WECHAT, ALIPAY, UNKNOWN;

    @JsonCreator
    public static Channel of(String raw) {
        return Arrays.stream(values())
            .filter(v -> v.name().equalsIgnoreCase(raw))
            .findFirst().orElse(UNKNOWN);   // 未知值给兜底,不抛异常
    }
}

容忍度是双向的:只要一端不宽容,"加可选字段"就不再兼容。这条要写进契约,不该靠自觉。

4. 开新版本的三个判据

三条必须同时成立。缺一条,说明还有别的路。

判据 提问 不成立时的替代
确属破坏性 旧端不改代码会否出错 走兼容扩展
难以按期迁移 能否一周期内改完 排期迁移,只修 bug
不可共存 能否同进程按路由分流 并行版本,共享数据层

第三条最常被跳过:很多"必须新版本"的场景其实能让旧路径走旧实现、新路径走新实现,共用数据层。真不可共存的,通常是数据模型与核心语义都变了。

// 开版决策清单(任一为"否"即回到兼容扩展)
// 1) 旧客户端原样读到的结果会错吗?否 → 不需要新版本
// 2) 调用方能在 DEPRECATION_WINDOW 内迁完?是 → 排期迁移
// 3) 新旧契约能在同一发布单元共存?是 → 路由分流
// 4) 皆否 → 开版本,同时写下关闭条件

5. 共存期的成本结构

代价在四个维度上翻倍,最贵的是值班,最常被低估的是监控。

成本项 具体形态 缩放关系
测试 两套契约用例与基线 随版本数线性增长
监控 指标按版本打标签 标签基数组合膨胀
文档 每版一份示例与变更 线性,维护者不增
值班 告警要判断"哪一版坏了" 定位时间近似翻倍

version 标签一加,看板与告警基数变大,查询成本上升。共存期只给活跃版本建看板,旧版本留错误率与调用量两个数,够判断下线即可。

6. 弃用流程的必要环节

弃用不是"发公告然后关掉"。缺任何一环,最后都变成"旧版本还在,因为没人敢关"。

环节 产出 缺失后果
公告期 停止支持日与范围 称"没收到通知"
迁移工具 字段对照表或样例 成本转嫁,进度停滞
调用量观测 按调用方分组请求数 判断不了是否没人用
强制下线日 具体日期与动作 旧版本无限期存活
// 弃用声明:机器可读部分要被网关与文档同时消费
deprecated: true
sunset: "SUNSET_DATE"          // 与公告日一致,中途不改期
migration: "docs/migrate-v1-to-v2.md"
contact: "@platform-api"

公告写清四件事:改什么、何时生效、影响谁、怎么验证。最后一条最常漏,等于把不确定性留给对方。

7. "永久支持旧版本"的组织代价

不弃用通常不是不知道成本,而是没人愿承担"关掉后爆炸"的责任。责任悬空会沉淀为结构代价。

代价 机制 表现
功能双份实现 每版都满足新需求 出现"v1 也做一遍"
缺陷修复回移 修一处回移一次 遗漏回移,旧版带病
认知负担 新人要学两套语义 排障时看错版本文档
决策僵化 旧版约束反向限制新版 新设计兼容无人用的怪癖

可执行的缓解是给旧版本设"最低维护等级":只修安全与正确性,不做功能增强,并写进公告。

8. 内部与外部接口不是一套策略

同一套机制用在内网与开放平台,成本差一个量级。

维度 内部接口 外部接口
调用方 可控,个位数 不可控、匿名
升级协调 可约同一发布窗口 只能靠公告与宽限期
破坏性容忍度 较高 极低
版本机制 能不用就不用 必须有版本与弃用契约
观测要求 够判断下线 需按调用方细分

内部接口的最优解常是"不设版本号,只维护一份向前兼容契约 + 变更公告"。外部接口相反:版本号是给对方的安全边界。

9. 版本号放在哪里:由缓存与网关决定

路径、Header、Query 没有审美高下,只有与基础设施的匹配度。

形式 例子 缓存友好 网关路由 客户端改造
路径 /v2/orders 最好,键天然隔离 前缀匹配最省事 改 base URL
Header Accept-Version: 2 差,需 Vary 按 Header 匹配 改请求头
Query /orders?version=2 差,易污染缓存 需重写规则 改参数拼接
// 路径版本:缓存键天然按版本隔离
location /v1/ { proxy_pass http://app_v1; }
location /v2/ { proxy_pass http://app_v2; }

// Header 版本必须声明 Vary,否则缓存会把两版混在一起
add_header Vary Accept-Version;

判据只有三条:缓存是否按 URL 分键、网关能否按该维度路由、客户端 SDK 是否易改。三条都指向路径时,别为了"URL 干净"选 Header。

10. 灰度版本与并行版本不是一件事

两个词常被混用,但解决的问题与失败方式都不同。

项 灰度版本 并行版本
目的 验证新版行为 旧调用方继续可用
分流依据 流量比例、用户分组 版本号、路径
生命周期 天到周,验证即收敛 月到季,直到弃用完
失败方式 分组不可比,结论失真 旧版无人维护,长期带病
必备观测 分组错误率与延迟对比 按版本调用量与错误率
// 并行版本必须能回答"旧版还有谁在用",否则收敛不了
// sum(rate(http_requests_total{route="/orders"}[5m])) by (version, client_id)
// 判据:调用方集合连续 LINGER_WINDOW 未变 → 剩下的是困难户,需点名跟进

11. 用什么判断版本策略是否健康

度量 定义 健康信号
活跃版本数 近三十天有调用的版本 稳定在 2 以内
旧版调用占比 旧版 / 总请求 单调下降,不停平台期
迁移完成率 已迁 / 应迁 按公告期推进,末段点名
弃用周期 公告日到下线日 有上限且实际执行

平台期最值得警惕:迁到八成停住,剩下两成永久占住旧版本,等架构升级时一次性引爆成本。

12. 排错:版本策略常见故障

现象 原因 处理
旧版归零却不敢关 观测只到接口层 补 client_id 维度,点名下线
老客户端报错 "加字段"被判为破坏性 客户端改宽容解析
两版数据不一致 双写期两侧逻辑分叉 单点写单点读,另一侧只做投影
文档说弃用,实际仍在跑 公告与网关不同步 弃用状态由配置驱动
灰度指标不可对比 分母不同、窗口错位 同窗口同分组再比

13. 边界:什么情况下版本机制该压到最简

情形 判断依据 做法
单一内部调用方 一个消费方,可同步改 一句通知 + 同一发布窗口
调用方可同步升级 同一发布单元内 不设版本号,一起发
仅新增可选字段 旧端不改也不出错 直接发,不升版本
临时实验接口 生命周期以周计 用 feature flag 而非版本

反过来,一旦调用方匿名、难以协调发布窗口、或契约要被第三方长期依赖,版本与弃用机制就是必需——省下的流程成本会在第一次破坏性变更时全部付回来。

14. 自测

  1. 字段名与类型都没变,只把单位从元改成分,属于哪类变更?为什么最危险?
  2. 判断"该不该开新版本"的三个条件是什么?缺一条时通常的替代是什么?
  3. 为什么共存期最贵的成本是值班,而不是文档?
  4. 弃用公告里最容易被漏掉的是哪一条?
  5. 版本号放路径还是 Header,判断依据是哪三条?
  6. 灰度版本与并行版本在"失败方式"上有什么不同?
S18-A17 · 接口版本的成本:什么时候该开新版本
https://www.clxhxhhr.top/posts/6289/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。