Skip to content

Getting started

Tomas Lycken edited this page Oct 30, 2017 · 19 revisions

The RdbmsEventStore has a few common components, and a few components specific to the backing database engine.

1. Events 2. The Event Factory

The Event Serializer

The Event Registry

In order to keep track of your event types, and to handle serialization and deserialization of event payloads, you must build an event registry for your events. In its simplest form, it is just a bi-directional dictionary between strings (event names) and System.Types (event types). In most common use cases, you can use an AssemblyEventRegistry, that does a lot of the work for you:

var eventRegistry = new AssemblyEventRegistry(
    typeof(SomeEventType),
    namer: type => type.Name,
    inclusionPredicate: type => type.Name.EndsWith("Event"));

The namer and predicate are optional, defaulting to type => type.Name and type => true.

Event

If you don't want any extras, the default IEvent<TStream> has the following shape:

public IEvent<out TStreamId>
{
    TStreamId StreamId { get; }
    DateTimeOffset Timestamp { get; }
    long Version { get; }
    Type Type { get; }
    object Payload { get; }
}

The properties Type and Object refer to

The Write Lock

In order to ensure that you don't have multiple threads adding events simultaneously, your event store should depend on the write lock. There is an interface IWriteLock and a default implementation WriteLock that just wraps an AsyncLock from Stephen Cleary's AsyncEx suite:

var writeLock = new WriteLock();

It is very important that the write-lock instance is singleton across the application. Otherwise, it won't do much good...

This is where it gets interesting, but also where we have to split up based on the backing data storage you'll be using.

Choosing a backing data store

Currently, only EF6 is implemented, but there are plans for at least EF Core and SQL Server without EF as well. From here, continue the guide that's appropriate for your backing store, and then come back here for the final notes.

Getting Started: Using Entity Framework 6

Continued: using the event store

You should now have been able to create an event store instance, eventStore.

Writing events to the event store

In order to write events to the store, you take a dependency on the IEventWriter interface, implemented by the event store, and use its Commit method:

var event = SomeActionResultingInAnEvent(); // event is an instance of one of your POCO events
await eventStore.Commit(streamId, versionBeforeThisEvent, event);

It works with lists too, so if you have an action that results in multiple events, you can commit them in one go. The streamId is a way to shard the event list based on e.g. domain aggregates or features. The versionBeforeThisEvent is a long value included in the metadata of the last event you read from the store before you took the action, and is used for conflict detection (it must equal the highest version currently in the event store, for that stream).

Reading events from the store

You can fetch events using the IEventStream interface:

var events = eventStore.Events(streamId);

However, often you won't want all events from a stream for performance reasons. There is therefore also an overload that takes a projection from IQueryable<TEvent> to IQueryable<TEvent>, which lets you filter further:

var events = eventStore.Events(streamId, es => es.Where(e => e.Timestamp > lastKnownEvent));

Updating state of aggregates

Since the process of updating your application state based on the wrapper TEvent will, for each type of event, involve the same tedious process of unpacking and deserializing the payload, there is a Materializer class that can help you with that:

var materializer = new Materializer(eventRegistry, serializer);
var events = eventStore.Events(streamId);
var state = materializer.Unfold(initialState, events, (s, e) => s.Evolve(e));

Here, initialState is an instance of the same type as state and the lambda at the end is a calculation of the next state given the current state and an event. For example, if the state is the position on a chess board, and the event is a move, then the lambda should return the position on the board after the move.

Where to go from here

Now that you've seen the basics, you probably want to check out these pages:

Hooking up your IoC container