【问题标题】:Ensuring APIs are separate确保 API 是独立的
【发布时间】:2011-01-11 00:53:29
【问题描述】:

TL;DR:制作 API。不同版本需要不同的字段。教我,聪明的人。

我目前正在尝试找出制作版本化 API 的最佳方法。也就是说,我希望有一个/api/v1/projects.json 的URL 将显示具有一堆字段的项目列表和api/v2/projects.json 以显示具有单独字段的项目列表。

我已经考虑这个问题大约 15 分钟了,这可能意味着一切都错了。目前我的app/models/project.rb 文件中有这个:

def self.api_fields
  { 
    :v1 => ["name"],
    :v2 => ["name", "tickets_count"]
  }
end

然后我可以像这样在我的 API 控制器 (api/v1/projects_controller.rb) 中使用它:

def index
  respond_with(Project.all(:select => Project.api_fields[:v1]))
end

这很好,可以按我的意愿工作,但可能有更好的方法。那是你的任务!与我分享您的 API 制作智慧。

如果您提出的解决方案还允许我对模型对象的实例使用方法,例如 Project 方法上的 tickets_count 方法,则可以加分。

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

    我同意polarblau 的观点,即您应该为不同版本的 API 设置多个控制器。所以,我的目标是这个问题的加分点。

    我认为要存档调用#tickets_count 的能力,您必须覆盖模型的#as_json#to_xml 方法。我认为你必须这样做:

    api/v1/projects_controller.rb

    def index
      respond_with Project.all, :api_version => :v1
    end
    

    project.rb

    class Project < ActiveRecord::Base
      API_FIELDS = {
        :v1 => { :only => [:name] },
        :v2 => { :only => [:name], :methods => [:tickets_count] }
      }
    
      def as_json(options = {})
        options.merge! API_FIELDS[options[:api_version]]
        super
      end
    
      def to_xml(options = {}, &block)
        options.merge! API_FIELDS[options[:api_version]]
        super
      end
    end
    

    但是,如果您不介意控制器中的混乱,我认为在控制器中的 respond_with 调用中指定 :only:methods 可能也是一个好主意,因为您不必重写那些#as_json#to_xml 方法。

    【讨论】:

      【解决方案2】:

      就像评论一样:

      你看过这些了吗?

      http://devoh.com/posts/2010/04/simple-api-versioning-in-rails

      Best practices for API versioning?

      devoh.com 建议在路由级别拆分版本,这似乎是个好主意:

      map.namespace(:v1) do |v1|
        v1.resources :categories
        v1.resources :products
      end
      
      map.namespace(:v2) do |v2|
        v2.resources :categories, :has_many => :products
      end
      

      然后你可以使用不同的控制器来返回不同的字段。

      【讨论】:

      • 这是我已经在做的,使用不同的控制器返回不同的字段。我认为那部分我已经确定了。问题更像是“这是正确的做法吗?”
      • 对我来说感觉是个好方法,但这就是为什么我以“只是评论:”开头的原因——我对自己的智慧不够自信;)。
      • 您还应该有一个用于 API 控制器的目录结构,例如 app/controllers/api/v2/categories_controller.rb
      【解决方案3】:

      如您所知,问题在于,无论您公开什么,最终客户端都可以创建直接依赖项。话虽如此,如果您直接将模型暴露给世界,例如http://domain.com/products.json,当您更改产品型号时,您的选择数量有限:

      1. 最终客户端必须接受它并且其行为类似于“无模式数据库”。你说它会改变,瞧,它已经完成了(读取客户将不得不处理它)!
      2. 您向 API 添加了更类似于企业的版本控制。这意味着在更高级的级别上,您向最终客户公开的不是您的模型。相反,您公开了公共对象,而这些对象又可以进行版本控制。这称为数据传输对象 (http://en.wikipedia.org/wiki/Data_transfer_object)

      如果我们希望采用第二种方法,我们可以执行以下操作:

      class Project < ActiveRecord::Base
      end
      
      class PublicProject
        def to_json(version = API_VERSION)
          self.send("load_#{version}_project").to_json
        end
      
        private
          def load_v1_project
            project = load_v2_project
            # logic that transforms a current project in a project that v1 users can understand
          end
      
          def load_v2_project
            Project.find...
          end
      end
      

      希望对你有帮助。

      【讨论】:

        【解决方案4】:

        在 /api/v1 的路由中安装 Sinatra 应用以处理您的 API 调用。使添加新 API 变得更容易,并且在您弃用它之前仍然向后兼容。

        【讨论】:

        • 它适用于 Rails 3 in Action。由于这个原因,我不想为此使用 Sinatra。我认为 Rails 也应该能够做到这一点。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-22
        • 1970-01-01
        • 2011-06-15
        • 2014-12-20
        • 2014-09-01
        相关资源
        最近更新 更多