Clamour
Fancy messaging library for Ruby. It could, and should be used as a basis for asynchronous systems written in Ruby. It uses RabbitMQ as a transport mechanism, and Sidekiq as a substrate to run message handlers.
Installation
Add this line to your application's Gemfile:
gem 'clamour'
And then execute:
$ bundle
Or install it yourself as:
$ gem install clamour
Configuration
Configuration of the messaging system is contained in an instance of Clamour::Configuration
class.
You could instantiate it using a hash of parameters:
configuration = Clamour::Configuration.new(logger: MonoLogger.new(STDERR), enable_connection: false)
or you could use accessors:
configuration = Clamour::Configuration.new
configuration.exchange = 'com.example.exchange'
If you intend to use the default Clamour configuration stored in Clamour.configuration
, you could use a shortcut:
Clamour.configure do |config|
config.rabbit_mq.host = '127.0.0.1'
config.rabbit_mq.user = 'admin'
config.rabbit_mq.pass = 'Ad$1n'
config.exchange = 'com.example.exchange'
end
You could put configuration code like this in a Rails initializer.
NB. By default connection to RabbitMQ is disabled when Rails is in test mode.
Usage
Send a message
Clamour::Message
is just a fancy hash serialized into
JSON (using fabulous Oj gem) to be sent over RabbitMQ.
A message has a special attribute _type
which distinguishes different
messages, and is added to a hash of message attributes. By default it is set to a snake cased,
dot delimited message class name. For example,
class Foo::Blah
include Clamour::Message
attribute :bar, String
end
foo = Foo::Blah.new(bar: 'baz')
Oj.dump(foo, mode: :compat)
# => {"_type":"foo.blah","bar":"baz"}
To publish a message, call #publish
on it. By default the method uses global configuration in Clamour.configuration
.
If you want to publish the message to somewhere special, pass an additional parameter to call:
foo.publish
# is equal to
foo.publish(Clamour.configuration)
# but a call below is different:
foo.publish(white_rabbit_mq_configuration)
The method #publish
here really wraps an original message into a message of class Clamour::Message::Sent.
Only latter is really serialized and sent over the wire. So, effectively foo.publish
would send JSON like this:
{"_type":"clamour.message.sent","payload":{"_type":"foo.blah","bar":"baz"}}
To set attributes, please, see documentation on Virtus. If you intend to use more complex object as an attribute value than a String, Fixnum, or Boolean, make sure the value can be serialized to JSON. You can check it by doing something like this:
complex_object = ExtraComplexObject.new
Oj.dump(complex_object, mode: :compat)
For specific criteria making an object serializable, refer to Oj documentation.
Receive a message
To decide what action should be run when a message comes, Clamour::Registry
is used.
Effectively it maps message type to an array of handler classes.
To register a handler for a message one could use method #on
:
Clamour.registry.on Foo::Blah => Foo::Blah::Receive
or employ a shortcut for mass-registration:
Clamour.registry.change do
on Foo::Blah => Foo::Blah::Receive
on Rabbit::White::Appeared => Rabbit::White::Follow
end
Handler registration could be put inside Rails initializer.
Clamour::Bus#subscribe
gets every JSON delivery from RabbitMQ, and sends it to a provided block.
Registry is then used to determine what handler to invoke:
bus.subscribe do |delivered_hash|
= delivered_hash[:_type]
registry.route() do |handler_class, |
# Instantiate handler and pass message
end
end
Do something
An actual handler must run independently of the subscription process. For this few instruments could be used. Clamour offloads handler running to Sidekiq. The previous code example effectively turns into
bus.subscribe do |delivered_hash|
message_type = delivered_hash[:_type]
registry.route(message_tye) do |handler_class, message_class|
handler_class.perform_async(message_class.new(delivered_hash) # Kind of
end
end
Real code is different, because of fancy fractal structure of the library: a message that you publish really is wrapped
inside Clamour::Message::Sent
, and intercepted later by a handler of class Clamour::Message::Receive
. The latter
routes wrapped message to an actual handler. You do not have to worry about it though.
A handler is a class that implements method on_message(message)
, and includes module Clamour::Handler
. You should expect message
argument to
be an instance of the message class that you set in registry. For a registry
Clamour.registry.change do
on Messaging::Foo::Done => Messaging::Blah::Do
end
class Messaging::Blah::Do
include Clamour::Handler
# @param [Messaging::Foo::Done] message
def ()
# Do Something
end
end
And now all together
If you use the gem inside a Rails application, create an initializer, for example in
"config/initializers/clamour.rb" to set up Clamour to accept message Messaging::Foo::Done
and pass it to a handler
Messaging::Blah::Do
:
require 'clamour'
Clamour.configure do |config|
config.exchange = 'special'
# And other changes to default configuration
end
Clamour.registry.change do
on Messaging::Foo::Done => Messaging::Blah::Do
end
Then you have to start a subsription process. If you use Rails, Sidekiq, and Foreman, all you need to do is to add a line to your Procfile:
subscriber: bundle exec rake clamour:subscribe
If the technological stack is different, you could figure out what to do just by looking at clamour:subscribe
rake task source code.
TODO
- More testing.
- Scheduled messages: send message in the future.
Contributing
- Fork it ( https://github.com/provectus/caruso/fork )
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request