
假设有下面一个场景: 1.0 版本客户端的用户注册功能,手机号是可选填的,即使用户不输入手机号也能注册,对应的后端接口 URL 是 v1/reg
但到了 1.1 版本,手机号改为了必填,这时候有两种做法:
1 )做一个新的 v2/reg 后端接口给客户端使用,缺点是,需要同时维护 v1 、v2 两个版本的接口,而且会造成两个接口的代码大量冗余,除了“手机号是否必填”不一样以外,其它功能的代码都一样
2 )让客户端把版本号传过来,后端接口根据客户端版本号来判断手机号是否必填,缺点是,接口里会充斥着很多对客户端版本号进行判断的代码,例如:
if (appVersion == "1.0") { // do something } else if (appVersion == "1.1") { // do something } else { // do something } 上面哪种做法更好呢?或者各位大神有没有更好的版本控制方案?
1 deplives 2021-06-14 19:45:14 +08:00 用路由去控制,domain/v1/xxxx domain/v2/xxxx 不要在业务代码里判断版本,这样后期维护成本太高了 |
2 Huiao 2021-06-14 19:49:56 +08:00 1.0 版本的代码和 1.1 版本的代码并存。网关通过 v1/v2 去转发 |
3 wellsc 2021-06-14 19:51:32 +08:00 header 加版本号 |
4 Huiao 2021-06-14 19:51:37 +08:00 1.0 和 1.1 的服务并存 |
5 Leigg 2021-06-14 19:56:53 +08:00 同 1 楼,不要在代码里面写 if elseif,会成为噩梦的开始, |
6 kaneg 2021-06-14 20:03:21 +08:00 URL 加版本 /v1/abc, /v2/abc 。既要支持多版本,又要代码完全不重复是不可能同时满足的,鱼与熊掌不可兼得。只能尽量通过抽取公共代码的方式来消除重复代码。毕竟对外的 API 就是这种特性,一旦暴露即等同于固化。除非根本就不管兼容性的问题。 |
7 ch2 2021-06-14 20:26:55 +08:00 通常的做法是 1 |
8 initd 2021-06-14 20:39:42 +08:00 GraphQL |
9 raaaaaar 2021-06-14 20:40:43 +08:00 via Android 我看见比较多的是 1,代码抽象出来更好,加分支的话。。 |
10 encro 2021-06-14 20:41:36 +08:00 首先排除一个最简单做法:新增一个 reg2 方法。 |
11 rb6221 2021-06-14 20:45:08 +08:00 你这样无论如何都要有 2 套逻辑共存。注意我说的是逻辑,无论是客户端还是后端,所以你维护两块代码是不可避免的事 |
12 fewok 2021-06-14 20:48:28 +08:00 有没有想过,这种不好做监控 |
13 9yu 2021-06-14 20:59:38 +08:00 via Android K8s 生态里面都是 /v1alpha/, /v1beta/ 这样的,短期可以同时保留 |
14 chinvo 2021-06-14 21:02:56 +08:00 via iPhone 正确做法是分开做 controller, 版本放到 url 或者 header 里 |
15 Maboroshii 2021-06-14 21:12:13 +08:00 新增一个 v2,然后 v1 除了修 bug 不维护了啊,后面的客户端更新就只能用 v2 |
16 rockyliang OP @kaneg 比较赞同你的看法,我目前能想到的也是只能通过抽取公共部分来消除不同版本接口之间的重复代码,感谢你的回答~ |
17 rockyliang OP @Maboroshii 新增一个 v2 的话,v2 和 v1 两个接口的代码就会大量重复,不过这种倒是可以通过抽取相同部分的代码来消除。v1 和 v2 两个接口只写差异部分的代码,相同部分的则通过调用共用函数 /方法来执行。感谢你的回答:) |
18 wd 2021-06-14 21:46:32 +08:00 via iPhone 和客户端对接的时候一定要注意客户端那边肯定是新旧版本同时共存的,他们一般没有办法控制用户那边的更新情况。所以你这个答案很明显,必须是共存。除非说不支持旧版了。 |
19 polyang 2021-06-14 21:50:21 +08:00 这种明显是用第一种方案。 在代码里判断版本号会成为你噩梦的开始 |
20 rockyliang OP @wd 其实问题里我提到的两个方法都是可以做到共存的,差别就在于:第一种需要新增一个接口;第二种不需要新增,但需要在同一个接口里判断客户端版本,然后执行不同的逻辑。 |
21 xckai123 2021-06-14 21:53:00 +08:00 新增一个 v2,等到 v1 确定没有流量再进来后,下一次 release 再把 v1 删掉,v2 改为 v1 |
22 rockyliang OP @polyang 是的,前面回答的同学也是这么说,所以第二种我不会考虑了,哈哈。感谢你的回答 |
23 axbx 2021-06-14 21:59:44 +08:00 网关做控制 根据账号来判断改走哪个接口 |
24 sutra 2021-06-14 22:57:31 +08:00 |
25 yeqizhang 2021-06-14 23:26:29 +08:00 via Android 我们之前用的 if 判断的,传的版本号应该用大于来判断而不是==,我寻思前面说用网关也是要写判断逻辑吧,只是换个地方写了 |
26 Luckydan 2021-06-15 08:44:56 +08:00 前端完全可以在发送请求的时候 |
27 Luckydan 2021-06-15 08:48:46 +08:00 前端在发送请求的时候无论是否手机号是必填项,都把手机号作为请求参数发到后端,后端根据手机号是否为空,再做对应的业务逻辑调用是不是稍微好点呢? |
28 eudore 2021-06-15 09:07:15 +08:00 新增一个接口很难?? |
29 Kyle18Tang 2021-06-15 09:29:58 +08:00 |
30 95276 2021-06-15 10:07:22 +08:00 趁这个机会提醒用户升级 |
31 WollensZhang 2021-06-15 10:26:59 +08:00 header 中添加 api release 信息,同时在 controller 层做版本隔离 |
33 yufeng0681 2021-06-15 10:54:38 +08:00 就你这个需求,手机号码在业务上已经要求是必填了。V1.0 版本的报错机制缺省就有, 后台判断不带手机号码,就返回报错,提示语: 手机号码必须填写。 不需要开发第二个接口 [针对你这个独特场景] |
34 Drc 2021-06-15 11:59:12 +08:00 正解 |
35 encro 2021-06-15 11:59:15 +08:00 @ak47007 本质上,其他办法只是换了个姿势加 reg3,reg4,reg5 。 对于注册这种,如果是内部不对外,影响人群不大,变更较快,没有必要采用 version,直接加一个 regWithMobile 方法可能是最简单的。不要将几分钟的活,变成几天的。将这个 version 加到变更日志和接口文档即可。 |
36 encro 2021-06-15 12:03:29 +08:00 以微信公证号接口为例:1.0-1.4 接口 url 其实是不变的,只是新增了其他方法,比如新增了新的分享到朋友圈方法,原来的分享朋友圈方法规定到某个版本废弃。 |
37 huifer 2021-06-15 12:32:11 +08:00 请求头 |
38 Rocketer 2021-06-15 13:04:46 +08:00 via iPhone 用 api gateway,默认各版本路由到相同的 handler,只把不同的部分路由到不同的 handler,这样就没有冗余了 |
39 rockyliang OP @yufeng0681 这样会导致使用旧版本客户端的用户不能注册,相当于强迫用户要升级到新版本,用户体验很差。当然问题中的手机号也只是我临时想到的一个例子,而实际中肯定也还有很多场景是有着 [ 新版本功能规则做了变更,但不能影响到旧版本的用户 ] 的要求 |
40 ltruntu 2021-06-15 14:40:12 +08:00 传入参数 加一个 判断参数 nginx 直接判断 1 就转发 1 的机器 2 就转发 2 的机器 类似灰度 |
41 walker2laok 2021-06-15 15:24:20 +08:00 沟通好只维护 3 个或其它个版本 [v2 、v3 、v3.1] ,如果 v1 来了,就提示强制升级 |
42 youngyezi 2021-06-15 15:28:44 +08:00 你这种情况,可以通过客户端上传版本号(版本号放 url,header 都可以)。项目代码里构造一个版本控制的中间件,通过中间件来转发:"把不同的版本转发到对应的版本下的 Action"。 1.这样不需要增加网关等其他额外服务 2.不需在每个方法体里做版本判断 3.多版本共存,哪个版本有变动只需要修改对应版本下的方法 |
43 zm8m93Q1e5otOC69 2021-06-15 19:02:00 +08:00 网关根据版本来控制好一些 |
44 zm8m93Q1e5otOC69 2021-06-15 22:10:50 +08:00 或者 header + filter 处理一下就行 |
45 samin 2021-06-16 10:42:07 +08:00 业内做法: 1. 两个项目,历史项目的接口用 /v1 标识区分,新项目用 /v2 标识区分,流量进入不同的项目互不影响 2. 同一个项目中,和 1 是一样的,只不过用不同的包来存放接口,一样用类似的方法来区分 不管哪种方式,谨记代码方法编写的`单一性`原则 |
46 dcoder 2021-06-16 12:51:49 +08:00 @rockyliang 其实方法(1),(2)最后都需要保存 2 套逻辑, 所以一定要做的事情是: factor out 共用逻辑,减少重复代码. 至于很多人说不能用(2), 其实也不一定, 因为很多小更新(v1.0.0 --> v1.0.1), 也不一定要新开个大版本号 (v1.0 -->v2.0) |
47 dcoder 2021-06-16 12:53:34 +08:00 像这种小更新(v1.0.0 --> v1.0.1), 也不一定要重新定义一个 api/path/v1.0.1 |
48 edk24 2021-06-16 15:34:40 +08:00 后台接口大多是要配置路由的,类似这段伪代码。多写一个接口就可以了 ``` web.get("/v2/user/register", function(req, resp){ }); web.get("v1/user/register", function(req, resp){ }); ``` |
49 hewiefsociety 2021-06-16 18:35:17 +08:00 就是一楼那么做 |