📤 Outboxer
Outboxer is an implementation of the transactional outbox pattern for Ruby on Rails applications.
It helps you migrate to event-driven architecture with at least once delivery guarantees.
Quickstart
1. Install gem
bundle add outboxer
bundle install
2. Generate schema migrations, publisher script and tests
bundle exec rails g outboxer:install
3. Migrate database
bundle exec rails db:migrate
4. Generate event schema and model
bundle exec rails generate model Event type:string created_at:datetime --skip-timestamps
bundle exec rails db:migrate
5. Queue outboxer message after event created
# app/models/event.rb
class Event < ApplicationRecord
after_create do |event|
Outboxer::Message.queue(messageable: event)
end
end
6. Derive event type
# app/models/accountify/invoice_raised_event.rb
module Accountify
class InvoiceRaisedEvent < Event; end
end
7. Create derived event type
bundle exec rails c
ActiveRecord::Base.logger = Logger.new(STDOUT)
Accountify::InvoiceRaisedEvent.create!(created_at: Time.current)
8. Observe transactional consistency
TRANSACTION (0.2ms) BEGIN
Event Create (0.4ms) INSERT INTO "events" ...
Outboxer::Message Create (0.3ms) INSERT INTO "outboxer_messages" ...
TRANSACTION (0.2ms) COMMIT
8. Publish outboxer messages
# bin/outboxer_publisher
Outboxer::Publisher.publish_messages(batch_size: 1_000, concurrency: 10) do |publisher, messages|
begin
# TODO: publish messages here
rescue => error
Outboxer::Publisher.update_messages(
id: publisher[:id],
failed_messages: messages.map do |message|
{
id: message[:id],
exception: {
class_name: error.class.name,
message_text: error.message,
backtrace: error.backtrace.join("\n")
}
}
end)
else
Outboxer::Publisher.update_messages(
id: publisher[:id],
published_message_ids: messages.map { |message| message[:id] })
end
end
To integrate with Sidekiq, Bunny, Kafka and AWS SQS see the publisher block examples.
Testing
To ensure you have end to end coverage:
bundle exec rspec spec/bin/outboxer_publisher
Monitoring
Monitor using the built-in web UI:
Publishers

Messages

Rails
# config/routes.rb
require 'outboxer/web'
mount Outboxer::Web, at: '/outboxer'
Rack
# config.ru
require 'outboxer/web'
map '/outboxer' { run Outboxer::Web }
Contributing
All contributions are welcome!
License
Open-sourced under LGPL v3.0.