Converse
Converse is an architectural / design tool that facilitates dependency inversion, separation of concerns and decoupling by providing a conversation-based API boundary.
Brokers know how to talk the language of a provider, and converses with such a provider using Interactions. Interactions discuss topics using technology specific conversations. Brokers speak two languages: the application's and a provider's. A broker is taught how to speak a provider's language by defining a set of interactions, which use provider-specific conversations to communicate. Responses from providers are processed / translated in the interactions. Formatting of responses and error handling are also processed in the interactions. Each interaction can interpret a response in a provider-specific way and translate the response into the application's domain language.
An application then relies on brokers to engage providers around topics, concerns and actions, by saying or asking and receiving the interpreted responses.
Modeling API interaction this way decouples the application from the providers. The brokers are in essence adapters that allow the creation of an application-specified API for interacting with providers, removing the application's dependency on providers. The application only depends on the API it specifies, i.e. the language it wants to talk.
Brokers become plug-in adapters to providers and can be swapped out at will, provided they can speak the application's language as well as the provider's language. The set of interactions a broker is aware of specifies the API towards the provider. The application specifies its API through a set of methods that ask brokers to act on its behalf.
Brokers can be chained for multiple translation / technology bridging.
Brokers and conversations supplied with the gem: HTML, REST, MySQL Brokers and conversations planned for future release: MCollective, Redis, ActiveMQ
This gem is sponsored by Hetzner (Pty) Ltd - http://hetzner.co.za
Installation
Add this line to your application's Gemfile:
gem 'converse'
And then execute:
$ bundle
Or install it yourself as:
$ gem install converse
Usage
In this example, a provider communicates RESTfully over HTTP. An application wants to talk with the provider, without engaging in the details of setting up an HTML conversation, and without specifying URL details. Specifically, the application wants to request a list of transactions from the RESTful transaction provider. The application might want to swap the RESTful provider out for a SQL provider, or some other provider at some stage.
The application specifies the API it wants to talk, and teaches a broker how to discuss transactions:
class ApplicationApi < Converse::API
def get_transactions(client_id)
substance = { :client_id => client_id }
o = GetTransactions.new(@broker, substance)
o.discuss
end
end
The GetTransactions interaction faces towards the provider, and uses a TransactionTranslator to turn JSON responses into a list of transactions that the application understands.
class GetTransactions < Converse::Interaction
def initialize(broker, substance)
ensure_that(substance).includes [:client_id]
ask_broker(broker).concerning("transactions").about("<client_id>/transactions.json").detailed_by(substance)
end
def interpret_conversation(response)
return [] if response.code == '404'
TransactionTranslator::build_financial_entries_from(response)
end
end
Generically, the provider interaction knows how to say and ask the provider for information:
class ProviderInteraction < Interaction
def ask
@conversation.ask(broker.prepare_content(@concern, @action), broker.generate_parameters(@substance))
end
def say
@conversation.say(broker.prepare_content(@concern, @action), broker.generate_parameters(@substance))
end
end
The RESTInteraction class provides some additional interpretation of responses. Interactions can rely on the RESTInteraction to call handle_error if "200 OK" is not received, and a formatting hook is provided:
class GetRevisions < Converse::RESTInteraction
def initialize(broker, substance)
ensure_that(substance).includes [:server]
ask_broker(broker).concerning("get_revision_list").about(substance)
end
def format_response(response_body)
RevisionFormatter::format_revisions_for_html(JSON.parse(response_body))
end
def handle_error!(response)
raise RuntimeError.new "#{response.code}, #{response.body}"
end
end
The ProviderBroker is taught how to talk the provider's language by using an HTMLConversation (or a technology specific conversation, e.g. SQL, Redis, etc.). The broker provides the mapping of concerns, actions and detail (substance) to the technology / domain specific format:
class ProviderBroker < Broker
attr_accessor :host
attr_accessor :port
attr_accessor :username
attr_accessor :password
def initialize(host, port , username, password)
@version = "v1"
@host = host
@port = port
@username = username
@password = password
end
def broker_conversation(topic)
conversation = HTMLConversation.new(topic)
conversation.username = @username
conversation.password = @password
conversation
end
def open_topic(concern, action)
"https://#{@host}:#{@port}/" + concern + "/" + action
end
def prepare_content(concern, action)
"/" + concern + "/" + action;
end
end
The application, a dependency injector or a factory at some point decides to use the ProviderBroker to facilitate interaction with the provider:
broker = ProviderBroker.new(@host, @port, @username, @password)
api = ApplicationApi.new(broker)
api.get_transactions('C0000001')
Contributing
- Please send me feedback by email ([email protected]) on this project and ideas around improving the architectural facilities provided by this gem.