No commit activity in last 3 years
No release in over 3 years
Event Sourcery with the conventions of Rails. Because combining two great powers is always better than not.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 1.1
~> 1.3

Runtime

~> 5.2.2
 Project Readme

EventSourcery::Rails

Build Status

I wanted to add Event Sourcing to my Rails application and after using several libraries/framework settled on EventSourcery from Envato. After creating a demo application based on their todo example with Rails I realized the installation steps are pretty simple. With this gem it can be installed and configured with a single rake task!

In addition to automating the install, I also wanted to incorporate the DSL from Sequent and RailsEventStore for commands and command handlers. I like the command handler itself wrapping single responsibility around an aggregate with the on command binding. I also wanted to remove the boilerplate for instantiating and validating commands since we can load ActiveModel::Validations for free.

  • Installation
  • Usage
    • Commands
    • Command Handlers
  • Contributing
  • License

Installation

Add the following line to your Gemfile.

gem 'event_sourcery'
gem 'event_sourcery-postgres'
gem 'event_sourcery-rails'

Then run bundle install

Next, your need to run the generator:

$ rails generate event_sourcery_rails:install

At this point you will have an initializer to configure EventSourcery and the following Rake tasks.

$ rails event_sourcery:db:migrate # create the event sourcery schema
$ rails event_sourcery:processors:setup # create projector schemas
$ rails event_sourcery:processors:reset # drop and recreate projector schemas and data
$ rails event_sourcery:processors:run # start event stream processors

Typically you'll have the following in your Procfile.

web: rails server
processors: rails event_sourcery:processors:run

Usage

Commands

EventSourcery::Rails adds an optional base class for commands to enforce instantiating commands with an aggregate_id and required parameters as keyword arguments. Defined attributes are available with an attr_reader.

Includes ActiveModel::Validations for validating attributes.

class AddUser < EventSourcery::Rails::Command
  attributes :name, :email
  validates_presence_of :name, :email
end

AddUser.new # => raises ArgumentError.new("missing keywords: aggregate_id, name, email")

command = AddUser.new(aggregate_id: 'aggregate-id',
                      name: 'name',
                      email: 'email')
command.aggregate_id # => "aggregate-id"
command.name # => "name"
command.email # => "email"

command.valid? # => true

Command Handlers

You can also optionally include EventSourcery::Rails::CommandHandler to use use a callback DSL for binding commands. This DSL allows your application code to use all command handlers with #call.

Todo

  • Consider switching to a base class with common initializer and with_aggregate
  • Introduce API for invoking all known command handlers with an array of commands.
class UserCommandHandler
  include EventSourcery::Rails::CommandHandler

  attr_reader :repository

  def initialize(repository: EventSourceryRails.repository)
    @repository = repository
  end

  on AddUser do |command|
    aggregate = repository.load(UserAggregate, aggregate_id)
    aggregate.add(name: command.name,
                  email: command.email)
    repository.save(aggregate)
  end

  on UpdateUserEmail do |command|
    aggregate = repository.load(UserAggregate, aggregate_id)
    aggregate.update_email(email: command.email)
    repository.save(aggregate)
  end
end

Contributing

Please submit issues and pull requests for bugs, features or ideas.

License

The gem is available as open source under the terms of the MIT License.