EventStore
A fast, production ready Ruby implementation of an EventStore (A+ES). For more detail on what an EventStore is checkout what Gregg Young has to stay about it: http://codebetter.com/gregyoung/2010/02/20/why-use-event-sourcing/
Usage
Currently, EventStore
supports postgres
and vertica
adapters.
Connecting in Development or Test
EventStore.postgres(:development)
EventStore.postgres #test
EventStore.vertica(:development)
EventStore.vertica #test
Connecting in Production or Elsewhere
EventStore.custom_config(redis_config, database_config)
#The redis and database configs are the standard hashes expected by the Redis.new and Sequel.connect -- we just pass them directly in
Notes on Connecting
EventStore
expects a database calledhistory_store
to exist.postgres
will try to connect in dev and test mode as 'nexia:Password1@localhost'postgres
will assume a port of5432
if one is not supplied.vertica
expects to find an environment variable (VERTICA_HOST) to be set and will use this as the host in dev and test modevertica
will assume its default port if one is not supplied.vertica
will try to connect in dev and test mode as 'dbadmin:password@[vertica_host]'To find the ip address of vertica on your local box (running in a vm):
- open Settings -> Network and select Wi-Fi
- open a terminal in the VM
- do /sbin/ifconfig (ifconfig is not in $PATH)
- the inet address for en0 is what you want Hint: if it just hangs, you have have the wrong IP
Caveat Emptor
redis
, by default, is using database 15. Running the tests will DROP this database every time they run.
Data Structures
EventStore uses two simple data structures:
EventStore::Event = Struct.new(:aggregate_id, :occurred_at, :fully_qualified_name, :serialized_event, :version)
EventStore::SerializedEvent = Struct.new(:fully_qualified_name, :serialized_event, :version, :occurred_at)
aggregate_id
is a string uniquely identifying your aggregateoccurred_at
is a UTC timestamp (for simplicty, EventStore assumes all times given in UTC)fully_qualified_name
is the connonical name of the event whose data you are storingserialized_event
is a byte array representing the actual event data (in Vertica is aVARBINARY(1000)
in postgres it is aBYTEA
version
is the monotonically increasing version number of each event. This number should be incremented by one, starting at the client's current version (seeclient.version
below) for each event the client wants to store.
client = EventStore::Client.new(device.mac_address)
EventStore::Event.new(client.id, Time.now.utc, 'name_updated', DeviceNameUpdated.to_binary, client.version + 1)
EventStore::Events
are appended to the EventStore
and EventStore::SerializedEvent
are returned from it. The reason for the asymmetry is performance. When you are deserializing thousands or millions of events and they all have the same aggregate id, there is no reason to deserialize that field, and doubly so if you are using a column-store database like Vertica.
Usage
client = EventStore::Client.new(aggregate_id)
# transactionally append an array events to an aggregate's event stream
client.append(events)
# Get a list of events representing a snapshot of the aggregate's current state (fast)
client.snapshot
# Get an aggregate's entire event stream (can be very large)
client.event_stream
# Get an aggregate's event stream starting from a version
client.event_stream_from(347)
# event_stream_from optionally takes a limit
client.event_stream_from(347, 1000)
# Get the last event for an aggregate
client.peek
# Get the current version of an aggregate
client.version
# Drop all events associated with an aggregate, including its snapshot
client.destroy!
# Drop all events associated with ALL aggregate
EventStore.clear!
Rspec
To have event_store cleanup after each spec, drop this in your spec_helper.rb:
require 'event_store'
EventStore.postgres #connect to postgres for specs, if you like
RSpec.configure do |config|
config.after(:each) do
EventStore.clear!
end
end