对于互联网业务来说,服务的平滑升级是很重要的,必须解耦服务端和客户端,保障升级过程中现有功能的可用性。如果我们的API接口有了破坏性变更(比如返回结果层级变化),那势必影响客户端的调用。
针对这种情况,对于API的设计和实现者而言,必须考虑向后兼容性,但随着时间的推移往往兼容性的实现会变得非常复杂,因此引入API版本管理将能解决这个尴尬。而对于API的使用者而言,也可以灵活选择使用不同版本API,而不用担心API的兼容性问题。
API版本管理方案
通常来说,API的版本管理有2种方案:
1、URI
以v1或v2这种版本号为前缀:
请求:http://localhost:8080/v1/student
响应:{"name":"aa bb"}
请求:http://localhost:8080/v2/student
响应:{"name":{"firstName":"aa","lastName":"bb"}}
或者将版本以参数形式拼接到后面:
请求:http://localhost:8080/student/param?version=v1
响应:{"name":"aa bb"}
请求:http://localhost:8080/student/param?version=v2
响应:{"name":{"firstName":"aa","lastName":"bb"}}
优点:
- 不破坏现有浏览器缓存机制,服务端、中间层也不需要额外考虑headers的缓存控制。
- GET请求可以直接在浏览器执行。如果您有非技术消费者,那么基于URI的版本将更容易使用。
缺点:
- URI污染。
- API文档。如何让文档生成理解两个不同的url是同一服务的版本?
2、自定义Header
例:
请求:http://localhost:8080/student/header header:X-API-VERSION = 1
请求:http://localhost:8080/student/header header:X-API-VERSION = 2
优点:
- 如果是已经上线的服务,不会影响现有的客户端(默认没有版本)。
- 没有URI污染。
缺点:
- 滥用请求头。请求头并不是为版本控制而设计的。
- 缓存。不能仅仅基于URL缓存,需要考虑特定的请求头。
- 即使是GET请求,也不能在浏览器直接执行。
API版本控制原则
- 一旦发布,API应被视为契约,如果没有新版本,则不能被替换。
- 服务端上线后,客户端新版本才能上线。
- 必须所有API使用者迁移后,旧版本的API才能废除。
- API的输入参数或输出的数据结构如果有变化,或者JSON数据中有调用者使用的字段修改、删除,即视为破坏性变更,需要增加版本。
开发实践
通常来说,URI的方式更符合我们的直觉。
Node.js
以nestjs工程为例:
- 文件路径中带版本号。比如user.controller.v1.ts、user.controller.v2.ts,或者v1/user.controller.ts。
- 推荐使用装饰器统一处理版本的技术细节。比如
@ApiVersion('v1'),这样在ApiVersion这个装饰器中不管是变更URI还是自定义Header,都与每个Controller的方法的业务代码无关。 - 如果想要同一个函数支持多个路由,可以使用alias,使用方法参见github.com/nestjs/nest…或github.com/nestjs/nest…。
Deno
如果是Deno的oak_nest工程,请更新到v1.10.2版本,Controller、Get/Post等装饰器都添加了可选参数alias和isAbsolute,比如:
@Controller("user", {
alias: "/v1/user/",
})
export class UserController {
@Get("/info")
info(context: Context) {
context.response.body = "info";
}
}
或者:
@Controller("/user")
export class UserController {
@Get("/info", {
alias: "/v1/user/info",
})
info() {}
}
详见文档说明。
参考: