在日常开发中,API版本的迭代几乎是无法避免的事情。最近在项目中,我们遇到了需要在不影响旧客户端的前提下,发布新版接口的需求。
一开始,我也用了最常见的做法:
乍一看很直观,路径一目了然,新老版本分开,大家都这么干,好像也没毛病。
可惜,这种方式看似简单,实则问题重重。
路径版本化的“表面光鲜”
我们可能会这么设计控制器:
这在项目初期确实很“快”。但是随着版本增多,问题接踵而至:
- 控制器、测试、文档全要复制一份;
- 每多一个版本,代码重复度、维护成本、测试开销都指数上升;
- 文档冗余、客户端混乱、接口路径不稳定。
URL版本控制违背了 REST 的设计初衷
根据 RESTful 的核心理念:
URI 是资源的永久地址,应保持稳定性。
路径中包含版本号,就像是给图书的 ISBN 每年都改一次,不仅违背设计初衷,还容易造成客户端的严重耦合。
举个例子:
如果我们每年都改接口路径:
老系统不能删除,新系统又要兼容,最终的结果是:
- 控制器臃肿不堪;
- 老版本无法完全淘汰;
- 文档极度冗余;
- 客户端升级困难;
- 出现 404 时,用户只能摸黑报 bug。
更优雅的做法:基于请求头的版本控制
相比 URL 版本,通过 HTTP Header 来传递版本信息更贴合 REST 设计原则。
接口路径不变:
版本通过 Accept 头协商:
- 请求 V1:
- 请求 V2:
Spring Boot 3.4 实现方式
项目结构基于 com.icoderoad
包名:
VersionProvider 示例:
响应 DTO 接口:
不同版本可以实现不同的 DTO,比如 ProductV1Dto
, ProductV2Dto
。
优点解析
优势 | 说明 |
URI 稳定 |
永远不会改变 |
向后兼容 | 多版本共存,只需判断 Header |
更适配缓存 | 请求路径不变,缓存更好命中 |
遵循 REST 规范 | 与 Fielding 的 REST 理论保持一致 |
扩展建议:再往前走一步
增加 Swagger 兼容支持
使用 springdoc-openapi
,通过 API Group 实现版本文档拆分展示:
自定义注解 + AOP 做更细粒度的控制
你也可以用注解标识不同版本的方法,再配合 AOP 在运行时动态路由调用,非常优雅。
那 URL 版本化有没有场景?
有!但 仅限以下两种情况:
- 内部系统或微服务之间通信,版本变动可控
- 资源结构发生破坏性变化,无法向后兼容(例如合规要求)
除此之外,请尽量使用 Header 版本控制。
总结
- URI 是资源的“身份证”,不应频繁变化;
- 版本控制建议通过 HTTP Header 实现;
- 教育前端和客户端团队理解 Accept Header 的重要性;
- 采用统一控制器,降低代码重复,维护成本更低。
如果你还在用 /v1/xxx
的方式管理版本,或许可以思考下换个方式,拥抱更优雅的 REST 实践。