再把你的 API 版本管理搞砸了!

别再把你的 API 版本管理搞砸了!

如果你正在使用 /v1/products 和 /v2/products,那么这篇文章就是为你准备的

在这里插入图片描述

最近,我接到一个需求,要将我的 API 更新到一个新版本,同时还要运行旧版本以支持现有的客户端。

我原来的端点是 /products

于是,我想:

让我们把当前版本切换到 /v1/products,新版本切换到 /v2/products很简单,对吧?

GET /v1/products GET /v2/products

这种方法感觉“没错,就该这么做”,

但是

它违反了 REST 原则,还会让维护工作变得异常棘手。

简单性陷阱

@RestController  
@RequestMapping("/v1/products")  
public class ProductControllerV1 {... }

@RestController  
@RequestMapping("/v2/products")  
public class ProductControllerV2 {... }

没错,实现起来很快,对吧?嗯……但是,我们要维护两个控制器(还有测试、文档……)。

教程和遗留系统让 URL 版本控制变得很常见。如果我们在谷歌上搜索版本控制,会发现很多资料都提到了 URL API 版本控制。

为什么在 URL 中进行版本控制会破坏 REST?

在这里插入图片描述

URI 应该是永恒不变的

REST 提到,URI 是资源的稳定标识符。当我们对 URL 进行版本控制时:

  • 客户端会硬编码像 /v1/products 这样的路径,从而造成紧密耦合。
  • 更改 URI 就像更改一本书的 ISBN 一样——会破坏所有引用它的图书馆。

维护并不容易

几年后,

/v1/products /v2/products /v3/products /legacy/products

现在我们面临着以下问题

  • 代码重复。
  • 不能停用已弃用的版本(客户端仍在使用它们)。
  • 文档臃肿。

客户端静默失败

/v1/products 被弃用时:

  • 使用它的移动应用开始返回 404 错误。
  • 用户没有收到任何警告——只是功能无法使用。

RESTful 的 API 版本控制替代方案

在这里插入图片描述

通过请求头进行版本控制

使用 Accept 请求头来协商版本:

GET /products Accept: application/vnd.myapi.v2+json
@GetMapping(value = "/users", produces = {  
"application/vnd.myapi.v1+json",  
"application/vnd.myapi.v2+json"  
})  
public ResponseEntity<?> getUsers(HttpServletRequest request) {  
String acceptHeader = request.getHeader("Accept");  
if (acceptHeader.contains("v2")) {  
return ResponseEntity.ok(userService.getUsersV2());  
} else {  
return ResponseEntity.ok(userService.getUsersV1());  
}  
}

优点

  • URI 保持稳定(/products 永远不变
  • 向后兼容性更容易实现(从一个端点提供多个版本)。
  • 缓存自然生效(版本是请求头的一部分)。

缺点

  • 客户端必须配置请求头(一些工具/库使得这并不容易实现)

Stripe:通过使用请求头,在不使 URL 变得杂乱的情况下将 API 版本扩展到了 10 多个。

通过自定义媒体类型进行版本控制

为什么这种方法可行

媒体类型(如 application/vnd.github.v3+json)明确声明了格式版本,从而将版本控制与 URI 解耦。

如何实现

为每个版本定义媒体类型:

GET /users HTTP/1.1  
Accept: application/vnd.company.products.v2+json

服务器端逻辑

  • 解析 Accept 请求头以确定版本。
  • 以请求的格式返回数据。

实际示例

  • GitHubAccept: application/vnd.github.v3+json
  • StripeAccept: application/vnd.stripe.v2+json

优点

  • 一种清晰、标准化的版本协商方式。
  • 支持在媒体类型中使用语义版本控制(例如,v1.2.3

缺点

  • 需要文档来指导客户端

GitHub:通过媒体类型多年来一直保持着向后兼容性。

为什么这些替代方案比 URL 版本控制更好

  1. 稳定的 URI:客户端收藏的是 /products,而不是 /v1/products
  2. 向后兼容性:从同一个端点提供旧版本和新版本。
  3. 代码更简洁:没有重复的控制器或路由。
  4. 符合 REST 规范:符合 Fielding 的约束条件。

如果你无法摆脱 URL 版本控制,那么应该只在以下两种情况下使用:

  1. 内部 API(你所控制的微服务)。
  2. 改变资源标识的重大变更(如 GDPR 规定的结构变更)。

即便如此,也要问一问:“我们能不能通过请求头来进行版本控制呢?”

关键要点

  1. URI 是永恒的 —— 不要将它们与版本紧密耦合。
  2. 请求头优于 URL,有利于可扩展性和符合 REST 规范。
  3. 指导客户端使用 Accept 请求头——这是值得的。

同意吗?不同意? → 点赞 👏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值