【问题标题】:Design pattern to use results of different APIs使用不同 API 的结果的设计模式
【发布时间】:2018-11-04 12:13:46
【问题描述】:

我有两个具有不同资源的 API:

  • www.api-A.com**/consumers,

    返回:{consumers: ['mike', 'Anna', 'Danilo']}

  • www.api-B.com**/clients,

    返回:{clients: ['Jack', 'Bruce', 'Mary']}

我想在一个控制器中使用这两个结果。我想像只有一个一样对待他们。

我是否必须为每个 api 创建一个包装器,例如:

module ApiAWrapper
  #code here
end

module ApiBWrapper
  #code here
end

并在我的控制器中调用以下内容?

MyController
  def index
    @clients << ApiAWrapper.most_recent
    @clients << ApiBWrapper.most_recent
    @clients
  end
end

这样做,@clients 将是:

['mike', 'Anna', 'Danilo', 'Jack', 'Bruce', 'Mary']

这是使用这些具有相似响应的不同 API 的正确方法吗?是否有我可以使用的设计模式或者我应该阅读以指导我?

【问题讨论】:

  • 重复项呢?
  • 有时每个api的响应可能不同。但我也想避免重复。因为我不能拥有两个 Wrapper APi,而是 3 个,我们的 5 个或更多。
  • 由于您的目的,这可能不是正确的评论。如果那我道歉。 :-) 定义类怎么样?支持那些类似于模型的资源。或者看看这个宝石。 github.com/remiprev/her#multiple-apis 我想如果你必须打电话给3 our 5 or more 它将是连续的并且可能会影响响应。

标签: ruby-on-rails api design-patterns


【解决方案1】:

当我需要外部服务以通用方式响应时,我会实现一个解析器。在其他语言中,您可以使用接口来强制执行方法签名合同,but Ruby doesn't have this feature because of the duck typing

这个解析器可以是一个函数或一个模块。例如:

module GitHub
  class Service
    BASE_URI = 'https://api.github.com'

    def self.fetch
      response = HTTP.get("#{BASE_URI}/clients")
      raise GitHub::ApiError unless response.ok?
      Parser.new(response).to_common
    end
  end

  class Parser
    def initialize(response)
      @response = response
    end

    def to_common
      json_response = JSON.parse(@response)
      json_response[:customers] = json_response.delete :clients
      # more rules
      # ...
      json_response
    end
  end
end

好的,你去吧。现在您有了一个服务,用于获取和处理 HTTP 部分,以及解析器,它处理来自 HTTP 请求的响应正文。现在,假设您要使用另一个 API,例如 BitBucket API:

module BitBucket
  class Service
    BASE_URI = 'https://bitbucket.com/api'

    def self.fetch
      response = HTTP.get("#{BASE_URI}/customers")
      raise BitBucket::ApiError unless response.ok?
      Parser.new(response).to_common
    end
  end

  class Parser
    def initialize(response)
      @response = response
    end

    def to_common
      json_response = JSON.parse(@response)
      json_response[:clients] = (json_response.delete(:data).delete(:clients))
      # more rules
      # ...
      json_response
    end
  end
end

这样,您将使用相同的接口返回两个服务。要加入结果,您可以这样做:

data = [GitHub::Service.fetch, BitBucket::Service.fetch, ...]
names = data.map { |customer_list| customer_list[:name] }
names.uniq

【讨论】:

    【解决方案2】:

    无论如何,您都应该为 API 调用提供包装器,因为控制器应该包含尽可能少的逻辑。 无论如何,我会创建一个类Client,其方法是将一组客户端json 反序列化为一组客户端。这样,在两个包装器中,您都将调用此方法并返回准备好在控制器中连接的客户端数组。

    类似:

    class Client
      attr_accessor :name
    
      def initialize(client_json)
        @name = client_json['name']
      end
    
      def self.deserialize_clients(clients_json)
        clients_json.map{ |c| Client.new(c) }
      end
    end
    

    然后对于包装器:

    module ApiAWrapper
       def self.most_recent
         response = #api call here
         Client.deserialize_clients(JSON.parse(response.body))
       end
    end
    

    你怎么看?

    【讨论】:

    • 很好,这样我就可以在 ApiAWrapper 和 ApiBWrapper 中为您提供客户端。但是如果也调用这些包装器,我的控制器操作将如何?
    • 和你的问题一样。我仍然会将这些查询包装在另一个服务中,因此控制器不知道您正在查询两个不同的 API。可能是一个ClientService,其方法可以从两个 API 返回客户端数组,然后在您的控制器中执行 @clients = ClientService.all_clients
    猜你喜欢
    • 2011-05-31
    • 1970-01-01
    • 1970-01-01
    • 2011-11-02
    • 2011-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多