如何管理版本化API的底层代码库?

Dyl*_*tie 91 versioning rest api-versioning

我一直在阅读有关ReST API的版本控制策略,而且它们似乎没有解决您如何管理底层代码库的问题.

比方说,我们正在做了一堆破更改API的-例如,改变我们的客户资源,使其返回单独forenamesurname领域,而不是一个单一的name领域.(对于此示例,我将使用URL版本控制解决方案,因为它很容易理解所涉及的概念,但问题同样适用于内容协商或自定义HTTP标头)

我们现在有一个端点http://api.mycompany.com/v1/customers/{id},另一个不兼容的端点在http://api.mycompany.com/v2/customers/{id}.我们仍在发布v1 API的错误修正和安全更新,但新功能开发现在都专注于v2.我们如何编写,测试和部署对API服务器的更改?我可以看到至少两个解决方案:

  • 为v1代码库使用源代码控制分支/标记.v1和v2是独立开发和部署的,必要时使用版本控制合并将相同的错误修复应用于两个版本 - 类似于在开发主要新版本的同时管理本机应用程序的代码库,同时仍然支持以前的版本.

  • 使代码库本身了解API版本,因此您最终会得到一个包含v1客户表示和v2客户表示的单一代码库.将版本控制视为解决方案体系结构的一部分而不是部署问题 - 可能使用命名空间和路由的某种组合来确保请求由正确的版本处理.

分支模型的明显优势在于删除旧的API版本是微不足道的 - 只是停止部署适当的分支/标记 - 但如果您运行多个版本,最终可能会遇到一个非常复杂的分支结构和部署管道."统一代码库"模型避免了这个问题,但是(我认为?)会使得在不再需要时从代码库中删除已弃用的资源和端点变得更加困难.我知道这可能是主观的,因为不太可能有一个简单的正确答案,但我很想知道在多个版本中维护复杂API的组织如何解决这个问题.

Pal*_*tim 40

我已经使用了你提到的两种策略.在这两个中,我赞成第二种方法,更简单,在支持它的用例中.也就是说,如果版本控制需求很简单,那么请使用更简单的软件设计:

  • 较少的更改,较低的复杂性更改或较低的频率更改计划
  • 与代码库的其余部分基本正交的更改:公共API可以与堆栈的其余部分和平地存在,而不需要"过多"(对于您选择采用的那个术语的任何定义)代码中的分支

我没有发现使用此模型删除已弃用的版本非常困难:

  • 良好的测试覆盖率意味着剥离退役的API和相关的支持代码确保没有(良好,最小的)回归
  • 良好的命名策略(API版本的软件包名称,或者有点丑陋,方法名称中的API版本)使得查找相关代码变得容易
  • 跨领域的担忧更加困难; 必须非常仔细地权衡对核心后端系统的修改以支持多个API.在某些时候,版本化后端的成本(请参阅上面的"过多"评论)超过单个代码库的好处.

从减少共存版本之间的冲突的角度来看,第一种方法当然更简单,但维护单独系统的开销往往超过减少版本冲突的好处.也就是说,站起来一个新的公共API堆栈并开始在一个单独的API分支上进行迭代是很简单的.当然,几乎立刻就会产生代际损失,并且分支变成了混乱的合并,合并冲突解决方案,以及其他这样的乐趣.

第三种方法是在架构层:采用Facade模式的变体,并将您的API抽象为面向公众的版本化层,这些层与适当的Facade实例进行对话,后者又通过自己的API集与后端进行通信.您的Facade(我在之前的项目中使用了一个适配器)变成了自己的包,自包含且可测试,并允许您独立于后端和彼此迁移前端API.

如果您的API版本倾向于公开相同类型的资源,但具有不同的结构表示形式(如fullname/forename/surname示例),则此方法将起作用.如果他们开始依赖于不同的后端计算,则会变得稍微困难​​,例如,"我的后端服务已经返回错误计算的公共API v1中公开的复合兴趣.我们的客户已经修补了这种不正确的行为.因此,我无法更新在后端进行计算并将其应用到v2.因此我们现在需要分叉我们的利息计算代码." 幸运的是,这些往往不常见:实际上,RESTful API的消费者喜欢准确的资源表示而不是bug-for-bug向后兼容性,即使在理论上幂等特定GET资源的非破坏性变化中也是如此.

我很想知道你的最终决定.

  • 只是好奇,在源代码中,你是否在v0和v1之间复制了没有改变的模型?或者你有v1使用一些v0模型?对我来说,如果我在某些领域看到使用v0模型的v1,我会感到困惑.但另一方面,它会减少代码膨胀.对于处理多个版本,我们是否必须接受并使用重复代码来处理从未改变过的模型? (4认同)
  • 我不确定我是否遵循第三种方法的建议。是否有任何公开的示例代码结构? (2认同)

小智 13

对我来说,第二种方法更好.我已将它用于SOAP Web服务,并计划将其用于REST.

在编写时,代码库应该是版本感知的,但兼容性层可以用作单独的层.在您的示例中,代码库可以生成具有名和姓的资源表示(JSON或XML),但兼容性层会将其更改为仅具有名称.

代码库应该只实现最新版本,比方说v3.兼容层应该转换最新版本v3和支持的版本(例如v1和v2)之间的请求和响应.兼容层可以为每个支持的版本提供单独的适配器,可以作为链连接.

例如:

客户端v1请求:v1适应v2 ---> v2适应v3 ----> codebase

客户端v2请求:v1适应v2(跳过)---> v2适应v3 ----> codebase

对于响应,适配器仅在相反方向上起作用.如果您使用的是Java EE,那么您可以将servlet过滤器链作为适配器链.

删除一个版本很简单,删除相应的适配器和测试代码.


edm*_*sov 5

分支对我来说似乎更好,我在这种情况下使用了这种方法。

是的,正如您已经提到的那样-向后移植错误修复将需要一些努力,但是同时在一个源基础上支持多个版本(包括路由和所有其他内容)将需要您(如果不是更少的话,但至少是同样的努力),从而使系统变得更加强大。复杂而又庞杂的内部逻辑各不相同(在版本控制的某个时刻,您会明确地case()指出具有重复代码甚至更差代码的版本模块if(version == 2) then...)。同样不要忘记,出于回归目的,您仍然必须保持测试分支。

关于版本控制政策:我会保留当前最多-2个版本,而不再支持旧版本-这将为用户迁移提供动力。

  • 好吧,三年后我了解到原来的问题没有准确的答案:D。它非常依赖于项目。如果你有能力冻结 API 并且只维护它(例如错误修复),那么我仍然会分支/分离相关代码(API 相关的业务逻辑 + 测试 + 休息端点),并将所有共享的内容放在单独的库中(带有自己的测试) )。如果 V1 将与 V2 共存相当长的一段时间并且功能工作仍在进行中,那么我会将它们放在一起并进行测试(涵盖 V1、V2 等并相应命名)。 (2认同)