V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
rockyliang
V2EX  ›  程序员

后端接口如何做版本控制?

  •  2
     
  •   rockyliang · 2021-06-14 19:38:46 +08:00 · 7837 次点击
    这是一个创建于 1017 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假设有下面一个场景: 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
    }
    

    上面哪种做法更好呢?或者各位大神有没有更好的版本控制方案?

    49 条回复    2021-06-16 18:35:17 +08:00
    deplives
        1
    deplives  
       2021-06-14 19:45:14 +08:00   ❤️ 1
    用路由去控制,domain/v1/xxxx domain/v2/xxxx 不要在业务代码里判断版本,这样后期维护成本太高了
    Huiao
        2
    Huiao  
       2021-06-14 19:49:56 +08:00
    1.0 版本的代码和 1.1 版本的代码并存。网关通过 v1/v2 去转发
    wellsc
        3
    wellsc  
       2021-06-14 19:51:32 +08:00
    header 加版本号
    Huiao
        4
    Huiao  
       2021-06-14 19:51:37 +08:00
    1.0 和 1.1 的服务并存
    Leigg
        5
    Leigg  
       2021-06-14 19:56:53 +08:00
    同 1 楼,不要在代码里面写 if elseif,会成为噩梦的开始,
    kaneg
        6
    kaneg  
       2021-06-14 20:03:21 +08:00   ❤️ 3
    URL 加版本 /v1/abc, /v2/abc 。既要支持多版本,又要代码完全不重复是不可能同时满足的,鱼与熊掌不可兼得。只能尽量通过抽取公共代码的方式来消除重复代码。毕竟对外的 API 就是这种特性,一旦暴露即等同于固化。除非根本就不管兼容性的问题。
    ch2
        7
    ch2  
       2021-06-14 20:26:55 +08:00
    通常的做法是 1
    initd
        8
    initd  
       2021-06-14 20:39:42 +08:00
    GraphQL
    raaaaaar
        9
    raaaaaar  
       2021-06-14 20:40:43 +08:00 via Android
    我看见比较多的是 1,代码抽象出来更好,加分支的话。。
    encro
        10
    encro  
       2021-06-14 20:41:36 +08:00
    首先排除一个最简单做法:新增一个 reg2 方法。
    janus77
        11
    janus77  
       2021-06-14 20:45:08 +08:00   ❤️ 1
    你这样无论如何都要有 2 套逻辑共存。注意我说的是逻辑,无论是客户端还是后端,所以你维护两块代码是不可避免的事
    fewok
        12
    fewok  
       2021-06-14 20:48:28 +08:00
    有没有想过,这种不好做监控
    9yu
        13
    9yu  
       2021-06-14 20:59:38 +08:00 via Android
    K8s 生态里面都是 /v1alpha/, /v1beta/ 这样的,短期可以同时保留
    chinvo
        14
    chinvo  
       2021-06-14 21:02:56 +08:00 via iPhone
    正确做法是分开做 controller, 版本放到 url 或者 header 里
    Maboroshii
        15
    Maboroshii  
       2021-06-14 21:12:13 +08:00
    新增一个 v2,然后 v1 除了修 bug 不维护了啊,后面的客户端更新就只能用 v2
    rockyliang
        16
    rockyliang  
    OP
       2021-06-14 21:15:04 +08:00
    @kaneg 比较赞同你的看法,我目前能想到的也是只能通过抽取公共部分来消除不同版本接口之间的重复代码,感谢你的回答~
    rockyliang
        17
    rockyliang  
    OP
       2021-06-14 21:45:52 +08:00
    @Maboroshii 新增一个 v2 的话,v2 和 v1 两个接口的代码就会大量重复,不过这种倒是可以通过抽取相同部分的代码来消除。v1 和 v2 两个接口只写差异部分的代码,相同部分的则通过调用共用函数 /方法来执行。感谢你的回答:)
    wd
        18
    wd  
       2021-06-14 21:46:32 +08:00 via iPhone
    和客户端对接的时候一定要注意客户端那边肯定是新旧版本同时共存的,他们一般没有办法控制用户那边的更新情况。所以你这个答案很明显,必须是共存。除非说不支持旧版了。
    polyang
        19
    polyang  
       2021-06-14 21:50:21 +08:00
    这种明显是用第一种方案。
    在代码里判断版本号会成为你噩梦的开始
    rockyliang
        20
    rockyliang  
    OP
       2021-06-14 21:50:57 +08:00
    @wd 其实问题里我提到的两个方法都是可以做到共存的,差别就在于:第一种需要新增一个接口;第二种不需要新增,但需要在同一个接口里判断客户端版本,然后执行不同的逻辑。
    xckai123
        21
    xckai123  
       2021-06-14 21:53:00 +08:00
    新增一个 v2,等到 v1 确定没有流量再进来后,下一次 release 再把 v1 删掉,v2 改为 v1
    rockyliang
        22
    rockyliang  
    OP
       2021-06-14 21:53:30 +08:00
    @polyang 是的,前面回答的同学也是这么说,所以第二种我不会考虑了,哈哈。感谢你的回答
    axbx
        23
    axbx  
       2021-06-14 21:59:44 +08:00
    网关做控制 根据账号来判断改走哪个接口
    sutra
        24
    sutra  
       2021-06-14 22:57:31 +08:00
    yeqizhang
        25
    yeqizhang  
       2021-06-14 23:26:29 +08:00 via Android   ❤️ 1
    我们之前用的 if 判断的,传的版本号应该用大于来判断而不是==,我寻思前面说用网关也是要写判断逻辑吧,只是换个地方写了
    Luckydan
        26
    Luckydan  
       2021-06-15 08:44:56 +08:00
    前端完全可以在发送请求的时候
    Luckydan
        27
    Luckydan  
       2021-06-15 08:48:46 +08:00
    前端在发送请求的时候无论是否手机号是必填项,都把手机号作为请求参数发到后端,后端根据手机号是否为空,再做对应的业务逻辑调用是不是稍微好点呢?
    eudore
        28
    eudore  
       2021-06-15 09:07:15 +08:00
    新增一个接口很难??
    Kyle18Tang
        29
    Kyle18Tang  
       2021-06-15 09:29:58 +08:00
    95276
        30
    95276  
       2021-06-15 10:07:22 +08:00
    趁这个机会提醒用户升级
    WollensZhang
        31
    WollensZhang  
       2021-06-15 10:26:59 +08:00
    header 中添加 api release 信息,同时在 controller 层做版本隔离
    ak47007
        32
    ak47007  
       2021-06-15 10:28:13 +08:00
    @encro v3,v4,v5 是不是要新增 reg3 reg4 reg5 ?
    yufeng0681
        33
    yufeng0681  
       2021-06-15 10:54:38 +08:00
    就你这个需求,手机号码在业务上已经要求是必填了。V1.0 版本的报错机制缺省就有, 后台判断不带手机号码,就返回报错,提示语: 手机号码必须填写。 不需要开发第二个接口 [针对你这个独特场景]
    Drc
        34
    Drc  
       2021-06-15 11:59:12 +08:00
    👆🏻正解
    encro
        35
    encro  
       2021-06-15 11:59:15 +08:00
    @ak47007

    本质上,其他办法只是换了个姿势加 reg3,reg4,reg5 。

    对于注册这种,如果是内部不对外,影响人群不大,变更较快,没有必要采用 version,直接加一个 regWithMobile 方法可能是最简单的。不要将几分钟的活,变成几天的。将这个 version 加到变更日志和接口文档即可。
    encro
        36
    encro  
       2021-06-15 12:03:29 +08:00
    以微信公证号接口为例:1.0-1.4 接口 url 其实是不变的,只是新增了其他方法,比如新增了新的分享到朋友圈方法,原来的分享朋友圈方法规定到某个版本废弃。
    huifer
        37
    huifer  
       2021-06-15 12:32:11 +08:00
    请求头
    Rocketer
        38
    Rocketer  
       2021-06-15 13:04:46 +08:00 via iPhone
    用 api gateway,默认各版本路由到相同的 handler,只把不同的部分路由到不同的 handler,这样就没有冗余了
    rockyliang
        39
    rockyliang  
    OP
       2021-06-15 13:49:26 +08:00
    @yufeng0681 这样会导致使用旧版本客户端的用户不能注册,相当于强迫用户要升级到新版本,用户体验很差。当然问题中的手机号也只是我临时想到的一个例子,而实际中肯定也还有很多场景是有着 [ 新版本功能规则做了变更,但不能影响到旧版本的用户 ] 的要求
    ltruntu
        40
    ltruntu  
       2021-06-15 14:40:12 +08:00
    传入参数 加一个 判断参数 nginx 直接判断 1 就转发 1 的机器 2 就转发 2 的机器 类似灰度
    walker2laok
        41
    walker2laok  
       2021-06-15 15:24:20 +08:00
    沟通好只维护 3 个或其它个版本 [v2 、v3 、v3.1] ,如果 v1 来了,就提示强制升级
    youngyezi
        42
    youngyezi  
       2021-06-15 15:28:44 +08:00
    你这种情况,可以通过客户端上传版本号(版本号放 url,header 都可以)。项目代码里构造一个版本控制的中间件,通过中间件来转发:"把不同的版本转发到对应的版本下的 Action"。
    1.这样不需要增加网关等其他额外服务
    2.不需在每个方法体里做版本判断
    3.多版本共存,哪个版本有变动只需要修改对应版本下的方法
    uselessVisitor
        43
    uselessVisitor  
       2021-06-15 19:02:00 +08:00
    网关根据版本来控制好一些
    uselessVisitor
        44
    uselessVisitor  
       2021-06-15 22:10:50 +08:00
    或者 header + filter 处理一下就行
    samin
        45
    samin  
       2021-06-16 10:42:07 +08:00
    业内做法:
    1. 两个项目,历史项目的接口用 /v1 标识区分,新项目用 /v2 标识区分,流量进入不同的项目互不影响
    2. 同一个项目中,和 1 是一样的,只不过用不同的包来存放接口,一样用类似的方法来区分

    不管哪种方式,谨记代码方法编写的`单一性`原则
    dcoder
        46
    dcoder  
       2021-06-16 12:51:49 +08:00   ❤️ 1
    @rockyliang
    其实方法(1),(2)最后都需要保存 2 套逻辑, 所以一定要做的事情是: factor out 共用逻辑,减少重复代码.
    至于很多人说不能用(2), 其实也不一定, 因为很多小更新(v1.0.0 --> v1.0.1), 也不一定要新开个大版本号 (v1.0 -->v2.0)
    dcoder
        47
    dcoder  
       2021-06-16 12:53:34 +08:00
    像这种小更新(v1.0.0 --> v1.0.1), 也不一定要重新定义一个 api/path/v1.0.1
    edk24
        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){

    });
    ```
    hewiefsociety
        49
    hewiefsociety  
       2021-06-16 18:35:17 +08:00
    就是一楼那么做
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3342 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 11:23 · PVG 19:23 · LAX 04:23 · JFK 07:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.