API 版本管理

API 版本管理是任何寻求持续演进而不破坏现有客户端集成的 Web 服务生命周期中的一项关键挑战。随着业务需求 的变化、缺陷的修复以及新功能的添加,API 必须以一种能够 实现创新而又不强迫所有使用方同时升级的方式进行演进。不恰当的 版本管理策略可能导致灾难性的场景,看似无害的更改会破坏 已经分发的移动应用程序(它们无法被强制更新)、依赖于特定 API 契约的合作伙伴系统,或处理金融交易的 业务关键型集成。在微服务架构中,这个问题变得更加复杂,因为多个 相互依赖的 API 必须以协调一致的方式演进;在公共 API 中也是如此,成千上万的第三方 开发者已在你的基础设施之上构建了解决方案。本文探讨了主要的 版本管理策略——包括 URI versioning、header versioning 和 content negotiation——分析每种方法的优点、 缺点和适用场景,此外还制定了弃用策略、 backward compatibility 策略以及变更沟通模式,从而在不损害依赖生态系统稳定性的前提下实现 API 的 可持续演进。

为何要对 API 进行版本管理

  • 不可避免的 Breaking changes:数据模型、认证、行为方面的更改
  • 异构客户端:移动应用、Web 应用、具有不同更新周期的合作伙伴
  • Backward compatibility:在过渡期间保持旧版本可用
  • 稳定的契约:为集成方确保可预测性
  • 有计划的弃用:以可控的方式对旧版本进行 sunset

版本管理策略

1. URI Versioning(最常见)

      # 在 URI path 中的版本
      GET /api/v1/users
      GET /api/v2/users
      # 优点:
      - 极其可见且明确
      - 易于测试不同版本
      - Cache-friendly(不同的 URL)
      - 在 proxies/gateways 中易于路由
      # 缺点:
      - 资源重复(v1/users, v2/users)
      - 可能导致 code duplication
      - 同一资源的 URL 发生变化
      

2. Header Versioning

      # Custom header
      GET /api/users
      API-Version: 2.0
      # Accept header (vendor MIME type)
      GET /api/users
      Accept: application/vnd.myapi.v2+json
      # 优点:
      - URI 保持简洁一致
      - 更符合 RESTful(同一资源,不同表示)
      - 可灵活地按资源进行版本管理
      # 缺点:
      - 可见性较低(需要检查 headers)
      - 增加手动测试难度
      - 缓存复杂(随 header 而变化)
      

3. Query Parameter Versioning

      # Query string
      GET /api/users?version=2
      GET /api/users?api-version=2.0
      # 优点:
      - 易于添加到现有 requests 中
      - 保持基础 URI 稳定
      - 对 HTTP 客户端而言简单
      # 缺点:
      - 可能污染 query parameters
      - 语义性较弱(版本并非过滤条件)
      - 存在 routing/caching 问题
      

4. Content Negotiation

      # Media type versioning
      GET /api/users
      Accept: application/vnd.company.user-v2+json
      # Schema versioning
      POST /api/users
      Content-Type: application/vnd.company.user.v2+json
      # 优点:
      - 更符合 RESTful 且 HTTP-compliant
      - 允许分别对 request/response 进行版本管理
      - 按 resource type 提供细粒度控制
      # 缺点:
      - 实现复杂
      - 需要理解 HTTP content negotiation
      - 调试更困难
      

面向 API 的 Semantic Versioning

      # MAJOR.MINOR.PATCH(为 API 调整的 Semver)
      MAJOR:Breaking changes
      - 移除 endpoints
      - 更改 response 结构
      - 更改认证
      - 示例:v1.0.0 → v2.0.0
      MINOR:Backward-compatible additions
      - 新增 endpoints
      - responses 中新增可选字段
      - 新增可选 query parameters
      - 示例:v2.0.0 → v2.1.0
      PATCH:Bug fixes
      - 不更改契约的修复
      - Performance improvements
      - 示例:v2.1.0 → v2.1.1
      # 沟通
      GET /api/v2/info
      {
      "version": "2.3.1",
      "deprecatedAt": "2025-06-01",
      "sunsetAt": "2025-12-01"
      }
      

Backward Compatibility

Backward-Compatible 更改

  • [OK] 新增 endpoints
  • [OK] 在 requests 中新增可选字段
  • [OK] 在 responses 中新增字段(客户端应忽略它们)
  • [OK] 将 required 字段改为 optional
  • [OK] 在现有 enums 中新增值
  • [OK] 放宽校验(接受更多 inputs)

Breaking 更改(需要新版本)

  • [X] 移除或重命名 endpoints
  • [X] 移除或重命名 responses 中的字段
  • [X] 更改数据类型(string → number)
  • [X] 在 requests 中新增 required 字段
  • [X] 收紧校验(拒绝先前接受的 inputs)
  • [X] 更改认证/授权行为

Deprecation Policy

      # 1. 弃用公告(提前 6-12 个月)
      {
      "data": [...],
      "deprecated": true,
      "deprecation": {
      "date": "2025-01-01",
      "sunset": "2025-07-01",
      "alternativeVersion": "v3",
      "migrationGuide": "https://docs.api.com/migrate-v2-to-v3"
      }
      }
      # 2. 弃用 Headers
      Deprecation: true
      Sunset: Wed, 01 Jul 2025 00:00:00 GMT
      Link: <https://docs.api.com/migrate>; rel="deprecation"
      # 3. 使用情况 Monitoring
      - 按版本记录 requests
      - 识别仍在使用 deprecated 版本的客户端
      - 主动通知开发者
      # 4. Overlap 期
      v2 Launch ─────────────────────────────►
      v3 Launch ─────────────►
      v2 Deprecated ─────►
      v2 Sunset
      

使用 Express.js 实现

      // Router-based versioning
      const express = require('express');
      const app = express();
      // V1 routes
      const v1Router = express.Router();
      v1Router.get('/users', (req, res) => {
      res.json({ version: 'v1', users: [...] });
      });
      app.use('/api/v1', v1Router);
      // V2 routes
      const v2Router = express.Router();
      v2Router.get('/users', (req, res) => {
      res.json({
      version: 'v2',
      users: [...],
      metadata: { ... }  // New in v2
      });
      });
      app.use('/api/v2', v2Router);
      // Header-based versioning
      app.get('/api/users', (req, res) => {
      const version = req.headers['api-version'] || '1';
      if (version === '2') {
      return res.json({ version: 'v2', users: [...] });
      }
      res.json({ version: 'v1', users: [...] });
      });
      

GraphQL Versioning

      # GraphQL 不需要传统的 versioning
      # 使用 schema evolution 和 @deprecated 指令
      type User {
      id: ID!
      name: String!
      email: String!
      username: String! @deprecated(reason: "Use 'name' field instead")
      }
      # Field-level deprecation
      type Query {
      users: [User!]!
      getUsers: [User!]! @deprecated(reason: "Use 'users' query instead")
      }
      # 增量更改天然就是 backward-compatible
      # 客户端仅请求它们所知道的字段
      

最佳实践

  • 选择一种策略并保持一致
  • 清晰地记录版本管理策略
  • 使用 semantic versioning 来传达更改的影响
  • 同时保持至少 2 个版本处于活跃状态
  • 在 responses 中实现 deprecation warnings
  • 提供详细的 migration guides
  • 按版本监控 usage metrics
  • 自动化 cross-version 测试
  • 提前沟通更改(changelog、邮件)
  • 考虑那些无法快速更新的客户端

最终建议

对于公共且长期存在的 API,URI versioning (/api/v1/) 因其清晰性和易用性通常是最佳选择。将其与 semantic versioning 相结合以传达更改的影响。对于内部微服务 API, 可考虑使用 header versioning 以获得更高的灵活性。在可能的情况下始终保持 backward compatibility,并制定明确的弃用策略,提供 宽裕的过渡期(至少 6-12 个月)。