Workflows are used to add additional functionality (other than updating a Projection) triggered by Events. Common tasks run by Workflows are:

  1. Execute other Commands
  2. Schedule something to run in the background

In Sequent, Workflows are committed in the same transaction as committing the Events.

Since Workflows have nothing to do with Projections they do not run when doing a Migration.

If you didn’t set enable_autoregistration to true you will need to add your Workflows manually to your Sequent configuration in order to use them:

Sequent.configure do |config|
  config.event_handlers = [
    SendEmailWorkflow.new,
  ]
end

A Workflow responds to Events basically the same way as Projectors do. For instance, a Workflow that will schedule a background Job using DelayedJob can look like this:

class SendEmailWorkflow < Sequent::Workflow
  on UserCreated do |event|
    Delayed::Job.enqueue UserJob.new(event)
  end
end

class UserJob
  def initialize(event)
    @event = event
  end

  def perform
    ExternalService.send_email_to_user('Welcome User!', event.user_email_address)
  end
end

If your Workflow has some side effects that can’t be rolled back easily, or if your background jobs processor is not using the same database connection used for the transaction, you can wrap it in an after_commit block:

class SendEmailWorkflow < Sequent::Workflow
  on UserCreated do |event|
    after_commit do
      SendEmailJob.perform_async(event)
    end
  end
end

class SendEmailJob
  include Sidekiq::Worker

  def perform(event)
    ExternalService.send_email_to_user('Welcome User!', event.user_email_address)
  end
end

It will run only if the transaction commits. Note that if you execute another command, it will be run synchronously but in a separate transaction. It will not be able to rollback the first one, resulting in some Events to be committed and some not. Only use after_commit if it is the intended behaviour.

Handling Exceptions: If an exception within an after_commit is not handled by the worker, it will stop calling the other registered hooks. Make sure that you rescue exceptions and handle them properly. If you can afford to ignore the errors and want to make sure all hooks are called, you can pass ignore_errors: true as a parameter.