Build Status Gem Version Code Climate Test Coverage Join the chat at https://gitter.im/arkency/rails_event_store

AggregateRoot

Event sourced (with Rails Event Store) aggregate root implementation.

Installation

  • Add following line to your application's Gemfile:
gem 'aggregate_root'

Before use

Choose your weapon now! Ekhm I mean choose your event store client. To do so add configuration in environment setup. Example using RailsEventStore:

AggregateRoot.configure do |config|
  config.default_event_store = RailsEventStore::Client.new
end

Remember that this is only a default event store used by AggregateRoot::Repository when no event store is given in constructor parameters. You could always set any event store client (must match interface) when creating AggregateRoot::Repository.

repository = AggregateRoot::Repository.new(YourOwnEventStore.new)
# do you work here...

To use RailsEventStore add to Gemfile:

gem 'rails_event_store'

Then setup RailsEventStore as described in Installation section of readme.

Usage

To create a new aggregate domain object include AggregateRoot::Base module. It is important to assign id at initializer - it will be used as a event store stream name.

class Order
  include AggregateRoot::Base

  def initialize(id = generate_id)
    self.id = id
    # any other code here
  end

  # ... more later
end

Define aggregate logic

OrderSubmitted = Class.new(RailsEventStore::Event)
OrderExpired   = Class.new(RailsEventStore::Event)
class Order
  include AggregateRoot::Base
  HasBeenAlreadySubmitted = Class.new(StandardError)
  HasExpired              = Class.new(StandardError)

  def initialize(id = generate_id)
    self.id = id
    self.state = :new
    # any other code here
  end

  def submit
    raise HasBeenAlreadySubmitted if state == :submitted
    raise HasExpired if state == :expired
    apply OrderSubmitted.new(delivery_date: Time.now + 24.hours)
  end

  def expire
    apply OrderExpired.new
  end

  private
  attr_accessor :state

  def (event)
    self.state = :submitted
  end

  def apply_order_expired(event)
    self.state = :expired
  end
end

Loading an aggregate root object from event store

repository = ArggregateRoot::Repository.new
order = Order.new(ORDER_ID)
repository.load(order)

Load gets all domain event stored for the aggregate in event store and apply them in order to given aggregate to rebuild aggregate's state.

Storing an aggregate root's changes in event store

repository = ArggregateRoot::Repository.new
order = Order.new(ORDER_ID)
repository.load(order)
order.submit
repository.store(order)

Store gets all unpublished aggregate's domain events (created by executing a domain logic method like submit) and publish them in order of creation to event store.

Resources

There're already few blogposts about building an event sourced applications wth rails_event_store and aggregate_root gems:

About

Arkency

Rails Event Store is funded and maintained by Arkency. Check out our other open-source projects.

You can also hire us or read our blog.