Toot 📯 💨
Toot is a library for basic PubSub over HTTP. Toot fires off event payloads, and provides lightweight Rack apps for handling event callbacks and subscription management that you can mount up in any Rack compatible app. All of these actions happen through Sidekiq providing asynchronous, persistent, and fault-tolerant events.
Usage
Toot is composed of several standalone components. An app can play the rolls of an event publisher, subscriber, or both. It just depends on how you configure it. Let's take a look at an example using two apps: "publisher" and "subscriber".
Overview
Here is how you could publish an event on the channel "users.updated.v1" from the "publisher" app and then subscribe to that channel on the "subscriber" app.
# Publish an event
Toot.publish "users.updated.v1", id: 123
# Subscribe to an event
Toot.subscribe :publisher, "users.updated.v1", -> (event) {
puts "User with id #{event["id"]} was updated."
}
The "subscriber" app has a named source
called "publisher" that is
configured in the config block. Let's take a look at what configuration
looks like on both apps. First let's take a look at the publisher:
# Configuration on the publisher
Toot.config do |c|
c.channel_prefix = "org.example.publisher."
end
# Mount up the subscriptions service (e.g. For Rails in routes.rb)
match "/subscriptions", to: Toot::SubscriptionsService, via: :post
We set the channel_prefix
which will prepend all events we publish
with this string. This ensures that we don't have name collisions with
other publishers we may introduce.
The Toot::SubscriptionsService
is a lightweight Rack app that can be
mounted up in any Rack compatible application. You can mount it at
whatever path you like. We'll need the full URL when we configure our
subscriber.
Let's take a look at the subscriber configuration now:
# Configuration on the subscriber
Toot.config do |c|
c.callback_url = "https://subscriber.example.org/callbacks"
c.source :publisher,
subscription_url: "https://publisher.example.org/subscriptions",
channel_prefix: "org.example.publisher."
end
# Mount up the handler service (e.g. For Rails in routes.rb)
match "/callbacks", to: Toot::HandlerService, via: :post
The source
option defines a new event source called "publisher". We
reference this name in any calls to Toot.subscribe
. We define the
subscription_url
to match the URL that we mounted up the
Toot::SubscriptionsService
Rack app on the publisher, and the
channel_prefix
should match the publisher's.
The Toot::HandlerService
is the Rack app that handles the event
callback from the publisher. The callback_url
configuration option
should match whatever URL you mount this Rack app to on your
application. This is used in the subscibe process to let the publisher
know how to notify us about a new event.
Event Lifecycle
If you're having trouble picturing how this is actually working let's trace the journey of a newly published event from the "publisher" to the "subscriber".
- Event is published using the
Event.publish
method. - This enqueues a background job which checks the Database (Redis) for subscribers to this channel and enqueues another job for each subscriber it finds.
- The subscriber's callback URL is called with the event's data payload
- The subscriber enqueues a background job if it has a handler for this event.
- This background job runs the actual event handler code.
Subscribing
Subscribing is handled by a callable class called
Toot::RegistersSusbcriptions
. There is also a rake task available that
does the same thing called toot:register_subscriptions
. If you are
using Rails this rake task will be available to you automatically.
Configuration
We've looked at an example, but let's look at the conifguration options in greater detail.
-
channel_prefix
: This option sets a string that will be prepended to all events published from this app. In all of our examples we've been using the reverse DNS notation, but you can use whatever convention you'd like. -
callback_url
: The event handler URL that this application will register to remote publishers. It should match the full URL to wherever you have mounted theToot::HandlerService
for this app. -
http_connection
: An instance of Faraday::Connection which is used for any external HTTP calls. You can use this to add custom middleware for authorization, logging, or debugging. -
redis_connection
: If you'd like to use a custom Redis connection you can configure a callable here that yields a redis connection. By default the Sidekiq Redis connection pool is used. -
source
: This is a method call that defines a named source. It should include asubscription_url
option and achannel_prefix
option.- The first argument is the name of this source
- The
subscription_url
option is the URL to this source'sToot::SubscriptionsService
app. - The
channel_prefix
should match this app's configured channel_prefix. This ensures that event channel names will be built correctly on the publisher side and on the subscriber side.
-
subscribe
: This is a method call that adds a subscription to a given source's channel. It takes three arguments:- The source name. This must match a previously defined source
- The channel suffix. This will be joined with the
channel_prefix
configured on the source to form the full channel name. - A callable event handler. The event object will be passed in as the only argument.
The Event Object
The schema of the publisher's payload is a contract between publisher and subscriber and is outside of the scope of this library. However, Toot does provide some extra information to aid in debugging and general happiness:
Toot.subscribe :publisher, "awesome.channel", -> (event) {
event.id # => "c6c4af6b-a227-4b0e-a7b5-5259d31cf98b"
event.timestamp # => 2015-10-23 11:50:09 -0500
event.channel # => "org.example.publisher.awesome.channel"
event.payload # => { "publisher_data" => 123 }
event["publisher_data"] # => 123
}
Authentication
It is important to note that there is no authentication baked into Toot's services. If you aren't running in a trusted environment (almost everybody) you will need to wrap these Rack apps in some kind of authentication middleware. In Rails, you can use the constraints routing config, or more generically, you can just decorate the service with another callable object that does the authentication checks.
To add authentication to outgoing requests you can build a Faraday
middleware and install it to the default connection using the
http_connection
configuration option.
Check out the toot-auth for an example implementation using HTTP Basic auth and credentials stored in Redis.
Installation
Add this line to your application's Gemfile:
gem 'toot'
And then execute:
$ bundle
Or install it yourself as:
$ gem install toot
Requirements
- Sidekiq (and therefore Redis)
- Ruby 2.1+ (or at least that's what's tested)
Development
After checking out the repo, run bin/setup
to install dependencies.
Then, run rake rspec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in
version.rb
, and then run bundle exec rake release
, which will create
a git tag for the version, push git commits and tags, and push the
.gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/watermarkchurch/toot.