确保 API 互操作性

OpenStack 的使命包括以下目标,适用于 OpenStack 云: “在部署之间可互操作”。 在 HTTP API 的上下文中,这意味着为云 A 编写的客户端代码也应在云 B 和任何其他云上工作。 由于云 A 和云 B 可能会在任何给定时间运行不同版本的 OpenStack 服务,并且它们可能会在不同的时间点升级其服务,因此这对 API 开发人员如何添加功能、修复错误以及从服务 API 中删除功能具有重要影响。

如果服务希望确保(并且应该确保)客户端代码始终与多个不同的 OpenStack 云互操作,那么

  • 这些更改不得违反兼容性或稳定性,这两者将在下面详细定义。

  • 在没有发出版本边界的情况下,不允许更改资源以及请求和响应主体和标头,除非在极少数情况下(如下文所述)。

    注意

    基于不同驱动程序可用性具有不同行为和表示形式的 API 对互操作性提出了重大挑战。 尽可能应避免这些差异。 如果无法避免,则应在云 A 和云 B 具有相同的驱动程序集可用时考虑互操作性。

  • 版本发现和选择应默认为基线;使用 API 中的新功能和更改应选择加入。

  • 当功能完全删除时,这必须导致基线提高。 理想情况下,这应导致主要版本更新,因为稳定性(向后兼容性)已被破坏。 当服务使用微版本时,“主要版本更新”通常意味着提高可用版本的最小值。

  • 如果将要删除功能,则其删除应遵循标准的 OpenStack 弃用程序。

  • 希望弃用某些功能但保持稳定性的服务可以通过将该功能记录为已弃用但将其保留在 API 中来欢迎这样做。

要点是,任何具有已发布 API 并需要对其进行更改(以修复或演进 API)的服务都需要一种版本控制机制。 本文档未涉及版本控制,但是 OpenStack 中唯一一种已被证明适用于此处目标的机制是 微版本

本文档的其余部分将描述服务如果希望确保 API 互操作性必须遵循的规则。 它提出了两个理解本文档所做的权衡的重要假设

  • API 的更改是不断发展的项目和多样化贡献者的不可避免的后果。 如果不太可能发生这种变化,则无需应用这些准则。

  • API 特别兴趣组的总体目标是鼓励所有服务的 API 一致性。 促进兼容性和稳定性的建议解决方案是实现一致性的指导。 关于何时更改会破坏互操作性的断言与任何给定的解决方案无关。

任何不支持版本控制并希望实现互操作性的服务应执行以下两件事

  • 添加对版本控制的支持。

  • 努力以诚信的方式遵循这些准则,并且在不可能的情况下,在考虑更改时要考虑到客户端代码中合理预期的行为。

注意

没有强大的系统来管理版本边界但必须更改其 API 的项目,从定义上来说,并没有遵守这些互操作性准则。 项目本身必须决定如何演进其 API。 如果这导致与 OpenStack 生态系统中的其他项目发生冲突,API-SIG 的作用仅是帮助澄清准则并就如何最大程度地减少更改的影响提供建议。 技术委员会是无法达成共识时提供裁决和调解的机构。

无论服务是否遵循准则,任何服务都应始终努力最大程度地减少 API 更改和版本升级,因为这会加剧用户“跟上”更改的需求。

一个新服务且正在开发中,显然会在其早期阶段进行大量的更改。 在此期间,版本控制不是稳定性的指标或工具。 但是,一旦进行了发布或与其他服务建立了依赖关系,如果需要互操作性,稳定性和兼容性就至关重要。

定义

为了便于理解这些准则,术语 兼容性稳定性 具有相当狭窄的定义

兼容性

如果为云 A 的该服务编写的客户端代码在云 B 上与同一服务一起工作而无需更改,则 API 具有兼容性。

稳定性

如果时间 X 编写的客户端代码在未来的时间 Y 继续工作而无需更改,则 API 是稳定的。

这些定义假定客户端代码编写遵循 HTTP 标准。

如果对如何将这些定义应用于特定情况有疑问,则应首先考虑 API 的最终用户(即希望在多个云上使用相同代码的个人)的观点和需求,其次是云的部署者和管理员,最后是服务的开发人员。

评估 API 更改

以下两种类型的更改不需要版本更改

  • 更改是必需的,以修复一个非常严重的安全性错误,需要将其回溯到服务的支持的所有版本。

    这种情况应该很少见。 对于许多不太严重的安全性情况,正确的答案是将其视为错误,进行修复,创建新版本并发布,并建议人们升级。 OpenStack 漏洞管理团队具有确定安全漏洞真实严重性的专业知识。

  • API 服务中的一个错误导致客户端收到状态代码在 500-599 范围内的响应,被修复为返回信息性错误响应在 400-499 范围(当请求有错误但可修复时)或响应成功(当请求正确形成,但服务器处理中断时)。

以下更改需要版本更改

  • 添加新的 URL。

    这可能看起来是向后兼容的,因为旧客户端永远不会使用新的 URL,但它破坏了呈现相同 API 版本但使用不同代码的云之间的互操作性。

  • 将响应状态代码从一种形式的客户端错误更改为另一种形式(例如,403400)或一种形式的成功更改为另一种形式(例如,201204)。

    关于更改成功代码,仍然存在争论。 如果客户端有效地能够通过更改成功代码,只要它将从 200299 的任何内容都视为成功即可。 这需要与这些准则假设的不同标准的客户端。 由于 OpenStack 生态系统中已经存在大量的客户端代码,因此强制执行客户端标准(例如 容忍读取器 概念)是不可能的。

  • 添加或删除请求或响应标头。

  • 更改响应标头的值,该值会更改客户端应如何处理响应。 例如,更改 Content-Type 标头的值以添加新的媒体类型。

  • 在请求或响应中的资源表示中添加或删除属性。

  • 更改资源表示(请求或响应)中现有属性的语义或类型。

  • 在保持其类型的同时,更改资源属性中允许的值集。

    例如,如果一个属性曾经接受“foo”、“bar”或“baz”,并且添加了“zoom”作为合法值,则需要进行版本控制。 如果删除了“foo”,也需要进行版本控制。 添加和删除都相关,因为我们希望两个不同的云在相同的 API 版本(但可能具有不同的代码版本)下表现相同。 为了实现这一点,即使是看似向后兼容的更改也需要版本更改。

如果进行了版本更改,则可以进行以下更改,但应充分考虑这对现有用户的影响。 在某个时候,用户将希望访问新版本中的新功能,或者服务的最低版本将提高到发生更改的版本之上。 客户端代码中的补偿更改在进行任何更改时都将非常重要,尤其是对于这些更改。

  • 与之前相比,请求现在导致错误响应(除非先前报告的成功隐藏了现有的错误条件)。

  • 删除 URL。

示例

在许多情况下,您会觉得更改是特殊的,并且保证违反这些准则。 请考虑以下场景

“更改是为了提高 API 的一致性。”

您提高 API 一致性的愿望值得赞赏,但所有 API 都有缺陷。 需要破坏性更改的不一致性可以在新的 API 版本中修复,但并非总是必要的。 另一个选项是添加具有不同行为的新 URL。 考虑所有选项,找到一种将您的精力集中于改善使用 API 的整体体验的方法。

“不太可能 API 的任何现有用户会受到影响。”

很难预测人们如何使用 API。 开发人员会做一些奇怪的事情。 随着我们的 API 越来越被采用,试图进行这样的预测将变得徒劳无功。 这种情况的一个例外是,正在更改的功能实际上是非功能的。

“现有的 API 没有很好地记录。”

如果 API 的行为没有充分 记录,那么使用 API 的开发人员除了根据他们观察到的行为来判断外,别无选择。 将违反这些观察的更改需要版本控制。

“更改不会影响 OpenStack 客户端库或命令行界面的用户。”

我们鼓励开发人员针对 OpenStack REST API 进行开发。 将会有许多工具和应用程序更喜欢 REST API 而不是我们的库或命令行界面。

新的或实验性的服务和版本控制

如上所述,一个全新的服务不应过早地承诺稳定性(如本文档中所定义)。 只有在达到某种形式的稳定性(标准英语意义上的稳定性)后,才值得考虑。

一个具有现有稳定服务并希望试验新功能的服务,可以选择永远不使该功能稳定,应将该实验性服务发布到服务目录中的唯一端点,与现有服务分开。

参考资料