我一直在阅读有关ReST API的版本控制策略,而且他们似乎没有解决您管理底层代码库的方法。
我们假设我们正在对API进行一系列重大更改 - 例如,更改我们的客户资源,以便它返回单独的forename
和surname
字段,而不是单个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的组织如何解决这个问题。
答案 0 :(得分:34)
我已经使用了你提到的两种策略。在这两个中,我赞成第二种方法,更简单,在支持它的用例中。也就是说,如果版本控制需求很简单,那么请使用更简单的软件设计:
我没有发现使用此模型删除已弃用的版本非常困难:
从减少共存版本之间的冲突的角度来看,第一种方法肯定更简单,但维护单独系统的开销往往超过减少版本冲突的好处。也就是说,站起来一个新的公共API堆栈并开始在一个单独的API分支上进行迭代是很简单的。当然,几乎立刻就会产生代际损失,并且分支变成了混乱的合并,合并冲突解决方案,以及其他这样的乐趣。
第三种方法是在架构层:采用Facade模式的变体,并将您的API抽象为面向公众的版本化层,这些层与适当的Facade实例进行对话,后者又通过自己的一组与后端进行对话。蜜蜂。你的Facade(我在之前的项目中使用了一个适配器)变成了自己的包,自包含且可测试,并允许你独立于后端和彼此迁移前端API。
如果您的API版本倾向于公开相同类型的资源,但具有不同的结构表示形式,那么这将起作用,如您的fullname / forename / surname示例中所示。如果他们开始依赖于不同的后端计算,则会变得稍微困难,例如,“我的后端服务已经返回错误计算的公共API v1中公开的复合兴趣。我们的客户已经修补了这种不正确的行为。因此,我无法更新在后端进行计算并将其应用到v2。因此我们现在需要分叉我们的利息计算代码。“幸运的是,这些往往不常见:实际上,RESTful API的消费者喜欢准确的资源表示而不是bug-for-bug向后兼容性,即使在理论上幂等GET
资源的非破坏性更改中也是如此。
我很想听听你最终的决定。
答案 1 :(得分:8)
对我而言,第二种方法更好。我已将它用于SOAP Web服务,并计划将其用于REST。
在编写时,代码库应该是版本识别的,但兼容性层可以用作单独的层。在您的示例中,代码库可以生成具有名和姓的资源表示(JSON或XML),但兼容性层会将其更改为仅具有名称。
代码库应该只实现最新版本,比方说v3。兼容层应该转换最新版本v3和支持的版本(例如v1和v2)之间的请求和响应。 兼容层可以为每个支持的版本提供单独的适配器,可以作为链连接。
例如:
客户端v1请求:v1适应v2 ---> v2适应v3 ---->代码库
客户端v2请求:v1适应v2(跳过)---> v2适应v3 ---->代码库
对于响应,适配器仅在相反方向上起作用。如果您使用的是Java EE,那么您可以将servlet过滤器链作为适配器链。
删除一个版本很简单,删除相应的适配器和测试代码。
答案 2 :(得分:2)
分支似乎对我来说好多了,我在这种情况下使用了这种方法。
是的,正如您已经提到的 - 向后移植错误修复将需要一些努力,但同时支持一个源基础下的多个版本(使用路由和所有其他东西)将要求您,如果不是更少,但至少相同的努力,使系统内部具有不同逻辑分支的系统变得更加复杂和怪异(在版本控制的某个时刻,你会定义为巨大的case()
指向具有代码重复的版本模块,或者甚至更糟糕if(version == 2) then...
)。
也不要忘记,为了回归目的,你仍然必须保持测试分支。
关于版本控制政策:我会保留当前最大-2版本,弃用对旧版本的支持 - 这会为用户提供一些动力。
答案 3 :(得分:0)
通常,引入API的主要版本会导致您不得不维护多个版本,这是一件不会(或不应)频繁发生的事件。但是,它不能完全避免。我认为总体上可以肯定的是,一个主要版本一旦推出,就会在相对较长的时间内保持最新版本。基于此,我宁愿以重复为代价来实现代码的简单性,因为它使我更有信心在引入最新版本的更改时不破坏先前版本。