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

Posted

技术标签:

【中文标题】您如何管理版本化 API 的底层代码库?【英文标题】:How do you manage the underlying codebase for a versioned API? 【发布时间】:2015-07-04 11:45:02 【问题描述】:

我一直在阅读有关 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 的组织如何解决这个问题。

【问题讨论】:

感谢您提出这个问题!我不能相信更多的人没有回答这个问题!我厌倦了每个人都对版本如何进入系统有意见,但似乎没有人解决将版本分配到适当代码的真正难题。到目前为止,对于这个看似常见的问题,至少应该有一系列公认的“模式”或“解决方案”。关于“API 版本控制”的 SO 存在大量问题。决定如何接受版本是 FRIKKIN 简单(相对)!一旦进入代码库,就很难处理它! 【参考方案1】:

你提到的两种策略我都用过。在这两个中,我更喜欢第二种方法,在支持它的用例中更简单。也就是说,如果版本控制需求很简单,那就选择更简单的软件设计:

更改数量少、复杂性低或更改计划频率低 在很大程度上与代码库的其余部分正交的更改:公共 API 可以与堆栈的其余部分和平共处,而无需在代码中“过度”(对于您选择采用的该术语的任何定义)分支李>

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

良好的测试覆盖率意味着删除已停用的 API 和相关的支持代码可确保没有(嗯,最小的)回归 良好的命名策略(API 版本的包名称,或者方法名称中有些丑陋的 API 版本)使查找相关代码变得容易 横切关注点更难;必须非常仔细地权衡对核心后端系统进行修改以支持多个 API。在某些时候,版本控制后端的成本(参见上面关于“过度”的评论)超过了单一代码库的好处。

从减少共存版本之间冲突的角度来看,第一种方法当然更简单,但维护单独系统的开销往往超过减少版本冲突的好处。也就是说,建立一个新的公共 API 堆栈并开始在一个单独的 API 分支上进行迭代是非常简单的。当然,代际损失几乎立刻就出现了,分支变成了一堆合并、合并冲突解决和其他类似乐趣的东西。

第三种方法是在架构层:采用 Facade 模式的变体,并将您的 API 抽象为面向公众的版本化层,这些层与适当的 Facade 实例通信,而后者又通过其自己的一组蜜蜂。您的 Facade(我在之前的项目中使用了一个 Adapter)变成了它自己的包,自包含且可测试,并允许您独立于后端迁移前端 API,并且彼此之间相互迁移。

如果您的 API 版本倾向于公开相同种类的资源,但具有不同的结构表示,这将起作用,例如您的全名/前名/姓氏示例。如果他们开始依赖不同的后端计算,就会变得稍微困难​​一些,例如,“我的后端服务返回了错误计算的复利,该复利已在公共 API v1 中公开。我们的客户已经修补了这种不正确的行为。因此,我无法更新它在后端计算并将其应用到 v2。因此我们现在需要分叉我们的利息计算代码。幸运的是,这些往往并不常见:实际上,RESTful API 的使用者更喜欢准确的资源表示,而不是逐个错误的向后兼容性,即使在理论上幂等 GETted 资源的非破坏性更改中也是如此。

我很想听听你的最终决定。

【讨论】:

只是好奇,在源代码中,您是否在 v0 和 v1 之间复制了未更改的模型?或者你有 v1 使用一些 v0 模型吗?对我来说,如果我看到 v1 在某些领域使用 v0 模型,我会感到困惑。但另一方面,它会减少代码膨胀。为了处理多个版本,我们是否只需要接受并使用从未更改过的模型的重复代码? 我的记忆是我们的源代码版本化模型独立于 API 本身,因此例如 API v1 可能使用 Model V1,API v2 也可能使用 Model V1。基本上,公共 API 的内部依赖图既包括公开的 API 代码,也包括后端“实现”代码,例如服务器和模型代码。对于多个版本,我曾经使用过的唯一策略是复制整个堆栈——一种混合方法(模块 A 被复制,模块 B 被版本化......)似乎很混乱。当然是 YMMV。 :) 我不确定我是否遵循第三种方法的建议。有没有类似结构的公开代码示例?【参考方案2】:

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

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

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

例如:

客户端v1请求:v1适配v2 ---> v2适配v3 ---->代码库

客户端 v2 请求:v1 适配 v2(跳过)---> v2 适配 v3 ----> 代码库

对于响应,适配器的作用只是相反的方向。如果您使用的是 Java EE,您可以将 servlet 过滤器链用作适配器链。

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

【讨论】:

如果整个底层代码库发生了变化,就很难保证兼容性。为错误修复版本保留旧代码库要安全得多。【参考方案3】:

分支对我来说似乎好多了,我在我的案例中使用了这种方法。

是的,正如您已经提到的 - 向后移植错误修复将需要一些努力,但同时支持一个源库下的多个版本(带有路由和所有其他东西)将需要您,如果不是更少,但至少是相同的努力,使用内部不同的逻辑分支使系统变得更加复杂和怪异(在某些版本控制时,您肯定会遇到巨大的case() 指向具有重复代码的版本模块,或者更糟糕的if(version == 2) then...)。 另外不要忘记,出于回归目的,您仍然必须保持测试分支。

关于版本控制政策:我将保留当前版本的最大 -2 个版本,弃用对旧版本的支持 - 这会给用户一些迁移的动力。

【讨论】:

目前我正在考虑在单个代码库中进行测试。您提到测试总是需要分支,但我认为 v1、v2、v3 等的所有测试也可以存在于同一个解决方案中,并且都可以同时运行。我正在考虑用指定它们支持的版本的属性来装饰测试:例如[Version(From="v1", To="v2")][Version(From="v2", To="v3")][Version(From="v1")] // All versions 现在只是在探索它,听说过有人这样做吗? 好吧,经过 3 年,我了解到原始问题没有准确的答案:D。它非常依赖于项目。如果您负担得起冻结 API 并且只维护它(例如错误修复),那么我仍然会分支/分离相关代码(API 相关业务逻辑 + 测试 + 休息端点)并将所有共享的东西放在单独的库中(带有自己的测试)。如果 V1 将与 V2 共存相当长的一段时间并且功能工作仍在进行中,那么我会将它们放在一起并进行测试(涵盖 V1、V2 等并相应地命名)。 谢谢。是的,这似乎是一个相当自以为是的空间。我将首先尝试一种解决方案,看看效果如何。【参考方案4】:

通常,引入主要版本的 API 会导致您不得不维护多个版本,这种情况不会(或不应该)频繁发生。但是,不能完全避免。我认为总体而言,一个主要版本一旦推出,将在相对较长的时间内保持最新版本是一个安全的假设。基于此,我宁愿以牺牲重复为代价来简化代码,因为这让我更有信心在引入最新版本的更改时不会破坏以前的版本。

【讨论】:

以上是关于您如何管理版本化 API 的底层代码库?的主要内容,如果未能解决你的问题,请参考以下文章

前端一个更底层库-React基础知识

如何查明网页中任何元素的底层页面代码?

YYModel底层解析- Runtime

webpack 底层原理

Azure API 管理 - 与底层服务进行身份验证和授权同步

探究一下c++标准IO的底层实现(3000字长文)