Files
2026-07-13 21:36:01 +08:00

7.6 KiB
Raw Permalink Blame History


API 版本控制策略

为什么要对 API 进行版本控制?

API 版本控制允许你在演进 API 的同时,保持对现有客户端的向后兼容性。破坏性变更需要发布新版本。

破坏性变更

需要发布新版本的变更:

  • 删除或重命名字段
  • 更改字段类型(如字符串改为整数)
  • 在请求中新增必填字段
  • 更改响应结构
  • 删除端点
  • 更改同一场景下的 HTTP 状态码
  • 更改认证机制

非破坏性变更

不需要发布新版本的安全变更:

  • 新增端点
  • 新增可选请求字段
  • 在响应中新增字段(客户端应忽略未知字段)
  • 修复 Bug
  • 性能优化
  • 为已有资源新增 HTTP 方法

版本控制策略

1. URI 版本控制

最常见且最直观的方式。版本号作为 URL 路径的一部分。

GET /v1/users/123
GET /v2/users/123

优点:

  • 在 URL 中清晰可见
  • 易于理解与实现
  • 路由和缓存简单
  • 可同时运行多个版本

缺点:

  • 违反 REST 原则(同一资源对应不同 URI)
  • 修改版本号需要更新客户端代码
  • 可能导致 URI 泛滥

实现方式:

/v1/users
/v1/products
/v2/users      # 包含破坏性变更的新版本
/v2/products

2. 标头版本控制

版本号通过 HTTP 标头(Accept 标头或自定义标头)指定。

Accept 标头:

GET /users/123
Accept: application/vnd.myapi.v1+json

GET /users/123
Accept: application/vnd.myapi.v2+json

自定义标头:

GET /users/123
API-Version: 1

GET /users/123
API-Version: 2

优点:

  • URI 保持稳定
  • 更符合 REST 风格(同一资源对应同一 URI)
  • 将版本控制与资源标识分离

缺点:

  • 不够直观(调试难度更大)
  • 路由更复杂
  • 在浏览器中测试困难
  • 缓存更复杂

3. 查询参数版本控制

版本号通过查询参数指定。

GET /users/123?version=1
GET /users/123?version=2

# 或
GET /users/123?api-version=1
GET /users/123?api-version=2

优点:

  • 实现简单
  • 易于测试
  • 在 URL 中可见

缺点:

  • 污染查询字符串
  • 不够语义化(版本不是筛选条件)
  • 可能与其他查询参数冲突

4. 内容协商

客户端通过内容协商指定所需的版本。

GET /users/123
Accept: application/vnd.myapi+json; version=1

GET /users/123
Accept: application/vnd.myapi+json; version=2

优点:

  • 非常符合 REST 风格
  • 灵活的内容类型协商
  • URI 稳定

缺点:

  • 实现复杂
  • 对开发者不够直观
  • 测试难度更大

推荐方案

推荐大多数 API 使用 URI 版本控制,原因如下:

  • 最明确、最易于发现
  • 易于理解和调试
  • 实现和维护简单
  • 版本之间界限清晰
/v1/users
/v2/users
/v3/users

版本格式

仅使用主版本号

公开 API 使用简单的主版本号(v1、v2、v3):

/v1/users
/v2/users

优点:

  • 简单明了
  • 易于沟通
  • 促使开发者慎重考虑破坏性变更

基于日期的版本号

部分 API 使用日期作为版本号:

/2024-01-01/users
/2024-06-15/users

使用者: Stripe、GitHub API

优点:

  • 版本发布时间一目了然
  • 时间线清晰易懂
  • 不会混淆主版本号与次版本号

缺点:

  • 对客户端不够直观
  • 难以了解具体变更内容

版本生命周期

1. 引入阶段

新版本与旧版本同时发布:

/v1/users  # 仍受支持
/v2/users  # 新版本可用

发布新版本时需公告:

  • 解释变更的博客文章
  • 迁移指南
  • 破坏性变更列表
  • v1 版本的弃用时间表

2. 弃用阶段

将旧版本标记为已弃用,但保持其继续运行:

GET /v1/users/123

响应:
Deprecation: true
Sunset: Wed, 15 Jan 2025 00:00:00 GMT
Link: </v2/users/123>; rel="successor-version"

{
  "id": 123,
  "name": "John Doe"
}

弃用标头:

  • Deprecation: true — 表示该版本已弃用
  • Sunset: <date> — 该版本将于何时移除(RFC 8594)
  • Link: <url>; rel="successor-version" — 指向新版本

3. 退役阶段

旧版本在公布的日期正式下线。

对已弃用的端点返回 410 Gone

GET /v1/users/123

响应:410 Gone
{
  "error": {
    "code": "VERSION_SUNSET",
    "message": "API v1 已于 2025-01-15 退役。请使用 v2。",
    "documentation_url": "https://api.example.com/docs/migration-v1-to-v2"
  }
}

弃用策略

推荐时间线

  1. 宣布弃用 — 至少在退役前 6 个月
  2. 支持期 — 两个版本同时运行 6-12 个月
  3. 退役日期 — 提前明确告知具体日期
  4. 宽限期 — 在完全关闭前,提供 30 天的 410 Gone 响应

沟通渠道

  • API 响应标头
  • 发送给注册开发者的邮件
  • 博客文章和更新日志
  • 控制台通知
  • 文档更新
  • 状态页面公告

迁移策略

提供迁移指南

# 从 v1 迁移至 v2

## 破坏性变更

### 用户资源变更

**v1**
```json
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com"
}

v2

{
  "id": 123,
  "first_name": "John",
  "last_name": "Doe",
  "email": "john@example.com"
}

迁移步骤:

  • name 字段拆分为 first_namelast_name
  • 更新客户端代码以使用新字段

### 提供辅助工具

- 迁移脚本
- SDK 更新
- API 差异对比工具
- 兼容层(临时)

## 版本发现

### 根端点

```http
GET /

响应:
{
  "versions": {
    "v1": {
      "status": "deprecated",
      "sunset_date": "2025-01-15",
      "documentation_url": "https://api.example.com/docs/v1"
    },
    "v2": {
      "status": "current",
      "documentation_url": "https://api.example.com/docs/v2"
    },
    "v3": {
      "status": "beta",
      "documentation_url": "https://api.example.com/docs/v3"
    }
  }
}

版本信息端点

GET /v2/version

响应:
{
  "version": "v2",
  "released": "2024-01-15",
  "status": "stable",
  "sunset_date": null
}

OpenAPI 版本控制

每个版本独立规范文件

openapi-v1.yaml
openapi-v2.yaml
openapi-v3.yaml

每个规范文件都是完整且独立的。

单一规范多服务器

openapi: 3.1.0
info:
  title: My API
  version: 2.0.0
servers:
  - url: https://api.example.com/v1
    description: 版本 1(已弃用)
  - url: https://api.example.com/v2
    description: 版本 2(当前版本)

最佳实践

  1. 从第一天起就做版本控制 — 从 /v1 开始,而不是 /api
  2. 仅使用主版本号 — 使用 v1、v2、v3(而不是 v1.1、v1.2
  3. 提供较长的弃用期 — 给客户端留出迁移时间(6-12 个月)
  4. 沟通要清晰 — 使用标头、文档、邮件
  5. 维护旧版本 — 至少同时支持两个版本
  6. 记录变更 — 提供详细的迁移指南
  7. 使用语义化版本控制 — 用于内部/SDK 版本控制
  8. 永远不要在没有警告的情况下破坏兼容性 — 始终提前公告破坏性变更
  9. 提供辅助工具 — 迁移脚本、更新后的 SDK
  10. 监控使用情况 — 追踪哪些版本正在被使用

反模式

避免以下错误:

  • 破坏性变更但不升级版本 — 破坏现有客户端
  • 版本过多 — 维护噩梦(最多保持 2-3 个活跃版本)
  • 弃用期过短 — 让开发者感到困扰
  • 没有迁移路径 — 让升级变得痛苦
  • 突然退役 — 毫无预警地破坏生产应用
  • 版本控制策略不一致 — 不同端点使用不同策略
  • 对单个端点进行版本控制 — 应在整个 API 中使用一致的版本