Reputation: 408
I have two APIs with different resources:
www.api-A.com**/consumers
,
which returns: {consumers: ['mike', 'Anna', 'Danilo']}
www.api-B.com**/clients
,
which returns: {clients: ['Jack', 'Bruce', 'Mary']}
I would like to use these two results in one controller. I want to treat them like if there were just one.
Do I have to create a wrapper for each api like:
module ApiAWrapper
#code here
end
module ApiBWrapper
#code here
end
and call the following inside my controller?
MyController
def index
@clients << ApiAWrapper.most_recent
@clients << ApiBWrapper.most_recent
@clients
end
end
Doing this, @clients
will be:
['mike', 'Anna', 'Danilo', 'Jack', 'Bruce', 'Mary']
Is this the right way to use these different APIs with similar responses? Is there a design pattern that I can use or I should read about to guide me?
Upvotes: 2
Views: 1668
Reputation: 6862
When I need external services to respond in a common way, I implement a parser. In other languages, you could use interfaces to enforce a method signature contract, but Ruby doesn't have this feature because of the duck typing.
This parser could be a function or a module. For example:
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
Ok, there you go. Now you've got a Service, for fetching and handling the HTTP part, and the Parser, that handles the response body from the HTTP request. Now, let's suppose that you want to use another API, the BitBucket API, for instance:
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
This way, you'll have both services returning using the same interface. To join the results, you could do:
data = [GitHub::Service.fetch, BitBucket::Service.fetch, ...]
names = data.map { |customer_list| customer_list[:name] }
names.uniq
Upvotes: 3
Reputation: 2841
You should have wrappers for your API calls anyway because the controller should have as little logic as possible.
Regardless, I would create a class Client
with a method to deserialize an array of client jsons into an array of clients. That way, in both wrappers you would call this method and return the array of clients ready to concat in the controller.
Something like:
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
Then for the wrappers:
module ApiAWrapper
def self.most_recent
response = #api call here
Client.deserialize_clients(JSON.parse(response.body))
end
end
What do you think?
Upvotes: 3