Mar*_*oot 11 api json ruby-on-rails rails-api active-model-serializers
我有一个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命名空间中的每个未更改的控制器,我们可以有一些stub v2控制器.这听起来也不是很好.
我能想到的最好的选择是在我的v2 API中有一个单独的控制器(在这种情况下可能只是一个串行器),有一些路由魔术可以检查是否存在所需版本的控制器并通过以前的版本回退直到找到一个.序列化程序版本也应具有类似的魔力,以检查此版本是否存在,并在找到之前回退.这引入了最少的额外代码,并且不会立即使我的测试套件的持续时间翻倍.它需要能够将函数直接插入到rails路由逻辑中,然后才能为我丢失的v2控制器返回404.可能我可以根据文件系统分析所有控制器的命名空间,并在rails启动时使用回退生成路由,但是很难明确地从先前版本的API中删除路由.
似乎我们需要继续为每个非附加功能/输出格式进行更改,直到每个先前版本被弃用和删除为止.我们有一个额外的未发布的API,包含约4000个控制器,涵盖~4000个规格.当我们开始从外部记录并发布这些内容时会发生什么?
除了按照我们发布功能的速率批量处理API更改之外,其他人如何管理?上面的想法是否可行?有没有更好的办法?
[1]问题一.我们使用ActiveModel :: Serializers来生成JSON响应.ActiveModel :: Serializers 不支持API版本控制,尽管似乎有一种方法可以使用ruby魔法来选择正确的类.
项目ActiveModel :: Serializers有与版本控制相关的数字问题,其中一个提供了如何通过命名空间模块实现版本控制的想法,但它在2天前关闭,后面是开发人员的一个词:
正如您所注意到的那样,我们已经讨论了其他问题和PR的版本控制,我很高兴看到你们所有人都非常好的想法.
因此,AMS版本控制的问题确实存在但尚未解决.
回到原来的问题:
很明显,我可以将所有app/api/v1复制到app/api/v2.然后,我可以对我的新v2序列化器进行单一更改.我现在已经为API的第2版获得了大量重复代码,几乎没有任何更改.我需要在两个地方维护代码.
继承复杂性与副作用VS代码重复之间存在折衷.如果经过充分测试的V1代码库应该被锁定以进行任何修改,那么维护确实意味着在运行回归测试套件时没有错误.版本1开发周期完成,测试编写,签订合同行为.代码重复V1-V2是有意义的,它避免了回归失败.
我可能不得不让我的整个rspec套件在版本2控制器以及版本1控制器上运行,并对v2序列化器进行一些额外的测试.这听起来像一个可怕的想法.
我不同意这是一个可怕的想法,这是在预期的行为和想象的便利与发展之间的权衡.避免重复规范套件也不容易.控制器,模型可以重复使用,但规范代码库更有可能重复,100%确信新的更改不会破坏以前的API版本.
我能想到的最好的选择是在我的v2 API中有一个单独的控制器(在这种情况下可能只是一个串行器),有一些路由魔术可以检查是否存在所需版本的控制器并通过以前的版本回退直到找到一个.
是的,这听起来不错,有助于避免应用程序代码(虽然不是规范套件)重复,但需要额外的开发工作和维护.您尝试执行的操作称为写时复制,仅复制更改.这是众所周知的优化技术.然而,HTTP回退听起来更合适.
可能我可以根据文件系统分析所有控制器的命名空间,并在rails启动时使用回退生成路由,但是很难明确地从先前版本的API中删除路由.
想象一下,你有超过2个版本的API,并且某个API调用有2个回退祖先,其中第二个被开发人员的错误打破,你是否会拦截404但是500例外?如果最新的DB方案版本破坏向后兼容性怎么办
我们有一个额外的未发布的API,包含约4000个控制器,涵盖~4000个规格.当我们开始从外部记录并发布这些内容时会发生什么?
这更像是架构问题而不是具体实现.如果API往往很大,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/;
}
Run Code Online (Sandbox Code Playgroud)
这个特殊的代码只是举个例子,我并不是说每个自己的版本都有10个不同的Rails应用程序是个好主意.
回到原始问题,API版本很难,对于某些API客户端,拥有默认(最新)API URL端点是有意义的.
如果我正确理解了您的所有需求,那么这对于路由 v2 请求来说不是足够的解决方案吗:
这是一个示例代码(上面列表中的每个步骤都有一个范围)
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
Run Code Online (Sandbox Code Playgroud)
关于序列化器,正如您自己提到的,通过在更改它们时始终提供新的控制器,或者甚至通过执行一些魔术来检查default_serializer_options中的 URL 的版本并基于该版本设置序列化器,可以轻松修复这些问题。
| 归档时间: |
|
| 查看次数: |
527 次 |
| 最近记录: |