对 Rails 3 API 进行版本控制的正确方法



我有一个 Rails 3 引擎,它公开了大约 20 个控制器的 API 路由。这些控制器代表不同嵌套级别的几种不同资源,并涵盖 500 多个 rspec 测试。API 在 v1 中使用命名空间和基于默认为 v1 的版本标头的路由约束进行版本控制。这是许多博客文章中描述的版本控制系统,似乎是最佳实践。

这些博客文章都没有描述您如何实际管理推出新版本。我必须对单个控制器的输出进行重大更改。此更改通过更改其中一个 JSON 值的结构来影响对象的 JSON 响应。这将导致该控制器的索引、显示和编辑视图中断。

很明显,我可以将所有app/api/v1复制到app/api/v2 [1]。然后,我可以对新的 v2 序列化程序进行单一更改。我现在为几乎没有进行任何更改的 API 版本 2 提供了大量重复代码。我需要在两个地方维护代码。我可能不得不让我的整个 rspec 套件在版本 2 控制器以及版本 1 控制器上运行,并对 v2 序列化程序进行一些额外的测试。这听起来像是一个可怕的想法。对于从 v1 控制器继承的 v1 命名空间中的每个未更改控制器,我们可以有一些存根 v2 控制器。这听起来也不是很好。

我能想到的最佳选择是在我的 v2 API 中有一个控制器(在这种情况下可能只是一个序列化程序),并带有一些路由魔法来检查所需版本的控制器是否存在,并回退到以前的版本,直到找到一个。序列化程序版本也应该具有类似的魔力,以检查此版本是否存在并回退,直到找到一个。这引入了最少的额外代码,并且不会立即使我的测试套件的持续时间加倍。它需要能够将函数直接插入 rails 路由逻辑,然后才能为我丢失的 v2 控制器返回 404。也许我可以基于文件系统分析所有控制器的命名空间,并在 rails 启动时生成带有回退的路由,但很难管理从以前版本的 API 中显式删除路由。

似乎我们需要继续为每个非附加功能/输出格式更改执行此操作,直到每个以前的版本都被弃用和删除。我们还有一个额外的未发布的 API,由 ~75 个控制器组成,涵盖 ~4000 个规范。当我们开始在外部记录和发布这些内容时会发生什么?

除了批量进行 API 更改之外,按照我们发布功能的速度是不可行的,其他人如何管理它?上面的想法可能吗?有没有更好的方法?

[1] 问题一。我们使用 ActiveModel::Serializers 来生成 JSON 响应。ActiveModel::Serializers 不支持 API 版本控制,尽管似乎有一种方法可以使用 ruby magic 来选择正确的类。

项目ActiveModel::Serializers有许多与版本控制相关的问题,其中一个提供了如何通过命名空间模块实现版本控制的想法,但它在2天前关闭,然后是开发人员的话之一:

正如您所注意到的,我们已经讨论了其他问题和 PR 的版本控制 以及,我很高兴从你们所有人那里读到如此美好的想法。

因此,AMS 版本控制的问题确实存在,但尚未解决。

回到最初的问题:

很明显,我可以将所有应用程序/api/

v1 复制到应用程序/api/v2。我可以 然后对我的新 v2 序列化程序进行单一更改。我现在有一个 具有以下特征的 API 版本 2 的大量重复代码 几乎没有进行任何更改。我需要在两个地方维护代码。

在继承复杂性与副作用与代码重复之间有一个折衷方案。如果具有经过良好测试的 V1 代码库,应该锁定以进行任何修改,则维护确实意味着在运行回归测试套件时没有错误。版本 1 开发周期完成,测试编写,合同行为签署。代码复制 V1-V2 有意义,它避免了回归失败。

我可能不得不让我的整个 rspec 套件在版本 2 上运行 控制器以及带有少量额外内容的版本 1 控制器 测试 v2 序列化程序。这听起来像是一个可怕的想法。

我不同意这是一个可怕的想法,这是预期行为和想象中的开发便利之间的权衡。避免规范套件被复制也不容易。控制器、模型可以重用,但规范代码库更有可能被复制,以 100% 确信新更改不会破坏以前的 API 版本。

我能想到的最佳选择是拥有一个控制器(在 这种情况可能只是一个序列化程序)在我的 v2 API 中,使用 一些路由魔术来检查控制器是否为所需版本 存在并回退到以前的版本,直到找到一个版本。

是的,这听起来不错,有助于避免应用程序代码(虽然不是规范套件)重复,但需要额外的开发工作和维护。您尝试执行的操作称为写入时复制,仅复制更改。这是众所周知的优化技术。尽管如此,HTTP回退听起来更合适。

可能我可以基于 文件系统并在 Rails 引导时生成具有回退的路由 但是很难管理从 以前版本的 API。

假设您有超过 2 个版本的 API,并且某个 API 调用有 2 个回退祖先,其中第二个被开发人员的错误破坏,您是否不仅拦截 404 个异常,还会拦截 500 个异常?如果最新的数据库方案版本破坏了向后兼容性怎么办?

我们还有一个额外的未发布的 API,由 ~75 个控制器组成 涵盖 ~4000 个规格。当我们从外部开始时会发生什么 记录并发布这些?

这更像是架构问题,而不是具体的实现。如果 API 往往很大,API 设计模式可以帮助避免构建难以支持和维护的整体式 API。

我建议做什么:

  • 完全复制 V1 到 V2,包括 rspec 套件
  • 不要害怕花费 2 倍的时间运行测试
  • 等到 AMS 发布版本控制(版本 v0.10.x)
  • 将整体式 API 拆分为基于单个的 API

如果代码重复不可接受,另一种选择是复制Rails应用程序并部署到同一服务器并使用Nginx配置调度请求:

location /v1 {
  proxy_pass http://http://unix:/tmp/v1_backend.socket:/v1/;
}
location /v2 {
  proxy_pass http://http://unix:/tmp/v2_backend.socket:/v2/;
}

这个特定的代码仅显示一个例子,我并不是说每个版本都有 10 个不同的 Rails 应用程序是个好主意。

回到最初的问题,API 版本控制很困难,对于某些 API 客户端来说,拥有默认(最新)API URL 端点是有意义的。

如果我正确理解您的所有需求,这是否足以路由 v2 请求:

  1. 检查 v2 下是否存在资源。
  2. 如果未找到,请检查它是否不是禁用的资源之一。如果是,则返回 404。
  3. v1 资源的回退(如果找到)。

下面是一个示例代码(上面列表中的每个步骤都有一个范围)

scope constraints: lambda { |request| request.url.split('api/')[1].split('/')[0] == 'v2' } do
  # New resources introduced in v2
end
# Resource was not found in v2 API, check if it is removed
scope constraints: lambda { |request| request.url.split('api/')[1].split('/')[0] == 'v2' } do
  # Resources removed from v2
  resources :resource1, to: proc { [404, {}, ['']] }
end
# Fallback for v2 routes that don't have v2 controller defined
scope constraints: lambda { |request| ['v1', 'v2'].include?(request.url.split('api/')[1].split('/')[0]) } do
  # Original v1 resources
end

关于序列化程序,正如您自己提到的,可以通过在更改它们时始终提供新的控制器来轻松修复它们,甚至可以通过执行一些魔术来检查 default_serializer_options URL 中的 URL 版本并基于此设置序列化程序。

相关内容

  • 没有找到相关文章

最新更新