No release in over a year
Setup event sourcing in a Rails application easily
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 6.0

Runtime

 Project Readme

Rails Event Sourcing

gem version linters specs

This gem provides features to setup an event sourcing application using ActiveRecord. ActiveJob is necessary only to use async callbacks.

DISCLAIMER: this project is in alpha stage

The main components are:

  • event: track state changes for an application model;
  • command: wrap events creation;
  • dispatcher: events' callbacks (sync and async).

This gem adds a layer to handle events for the underlying application models. In short:

  • setup: an event model is created for each "event-ed" application model;
  • usage: creating/updating/deleting application entities is applied via events;
  • every change to an application model (named aggregate in the event perspective) is stored in an event record;
  • querying application models is the same as usual.

⭐ if you like it, please.

A sample usage workflow:

# Load a plain Post model:
post = Post.find(1)
# Update that post's description:
Posts::ChangedDescriptionEvent.create!(post: post, description: 'My beautiful post content')
# Create a new post:
Posts::CreatedEvent.create!(title: 'New post!', description: 'Another beautiful post')
# List events for an aggregated entity (in this case Posts::Event is a STI base class for the events):
events = Posts::Event.events_for(post)
# Rollback the post to a specific version:
events[2].rollback!
# The aggregated entity is restored to the specific state, the events above that point are removed

ℹī¸ this project is based on the event-sourcing-rails-todo-app-demo proposed by Philippe Creux and his video presentation for the Rails Conf 2019 🚀

Usage

  • Add to your Gemfile: gem 'rails-event-sourcing' (and execute bundle)
  • Create a migration per model to store the related events, example for a User model: bin/rails generate migration CreateUserEvents type:string user:reference data:text metadata:text
  • Create the required events, example to create a User:
module Users
  class CreatedEvent < RailsEventSourcing::BaseEvent
    self.table_name = 'user_events' # usually this fits better in a base class using STI

    belongs_to :user, autosave: false

    data_attributes :name

    def apply(user)
      # this method will be applied when the event is created
      user.name = name
      # the aggregated entity must be returned
      user
    end
  end
end
  • Create an event (which applies the User creation) with: Users::CreatedEvent.create!(name: 'Some user')
  • Optionally define a create Command, for example:
module Users
  class CreateCommand
    include RailsEventSourcing::Command

    attributes :user, :name

    def build_event
      # this method will prepare the event when the command is executed
      Users::CreatedEvent.new(user_id: user.id, name: name)
    end
  end
end
  • Invoke it with: Users::CreateCommand.call(name: 'Some name')

Please take a look at the dummy app for a complete example. In this case I preferred to store events models in app/events, commands in app/commands and dispatchers in app/dispatchers - but this is not mandatory. Another option could be to have an Events namespace and a single event could be: Events::TodoItem::CreatedEvent.

Examples

Events:

TodoLists::Created.create!(name: 'My TODO 1')
TodoLists::NameUpdated.create!(name: 'My TODO!', todo_list: TodoList.first)
TodoItems::Created.create!(todo_list_id: TodoList.first.id, name: 'First item')
TodoItems::Completed.create!(todo_item: TodoItem.last)

Commands:

TodoLists::Create.call(name: 'My todo')
TodoItems::Create.call(todo_list: TodoList.first, name: 'Some task')

Dispatchers:

class TodoItemsDispatcher < RailsEventSourcing::EventDispatcher
  on TodoItems::Created, trigger: ->(todo_item) { puts ">>> TodoItems::Created [##{todo_item.id}]" }
  on TodoItems::Completed, async: Notifications::TodoItems::Completed
end
# When the event TodoItems::Created is created the trigger callback is executed
# When the event TodoItems::Completed is created a job to create a Notifications::TodoItems::Completed event is scheduled

To do

  • Generators for events, commands and dispatchers
  • Database specific optimizations
  • Add more usage examples

Do you like it? Star it!

If you use this component just star it. A developer is more motivated to improve a project when there is some interest.

Or consider offering me a coffee, it's a small thing but it is greatly appreciated: about me.

Contributors

License

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