-
Notifications
You must be signed in to change notification settings - Fork 1
Home
In many Rails apps, models are responsible for triggering everything:
- Sending emails
- Enqueuing jobs
- Syncing with external systems
- Updating dashboards
This leads to tight coupling—where core business logic is tangled with infrastructure code. Over time, changes become riskier, tests more brittle, and debugging harder.
Instead of directly triggering everything, a model just queues a message describing what happened. Other parts of the system can react later—without knowing or caring about the source.
module Accountify
class Invoice < ApplicationRecord
def finalise!
update!(status: 'finalised')
Outboxer::Message.queue(
messageable: self,
type: 'Accountify::Invoice::Finalised'
)
end
end
endThe model stays focused. The message acts as a signal that something meaningful occurred.
- Aggregates remain clean and focused
- Behavior can be extended without modifying core logic
- System is easier to test, understand, and evolve
If you update a record and publish an event in separate steps, it's easy to run into subtle, high-risk bugs:
- The record saves, but the event fails to publish
- The event publishes, but the database transaction rolls back
This leaves the system in an inconsistent state.
Outboxer uses the transactional outbox pattern to ensure event reliability. You use Outboxer::Message.queue inside the same transaction as your business logic:
ActiveRecord::Base.transaction do
contact = Contact.create!(...)
Outboxer::Message.queue(
messageable: contact,
type: 'Accountify::Contact::Created'
)
endThe message is only saved if the transaction commits successfully.
Messages are later published by a background job or loop:
Outboxer::Publisher.publish_messages do |_, messages|
messages.each do |message|
Sidekiq.perform_async("HandleMessageJob", message.payload)
end
end- Messages are only published after successful commits
- There’s no need to coordinate message delivery manually
- The system remains consistent, even during crashes or retries