Fork me on GitHub

Appending Events Edit on GitHub


Marten's event sourcing support "appends" event data documents to a single table mt_events. Events must be captured against a stream id, with a second table called mt_streams that Marten uses to keep metadata describing the state of an individual stream. Appending events to either a new or existing stream is done within the same Marten transaction as any other document updates or deletions. See Storing Documents and Unit Of Work for more information on Marten transactions.

Event Types

The only requirement that Marten makes on types used as events is that they are:

  1. Public, concrete types
  2. Can be bidirectionally serialized and deserialized with a tool like Newtonsoft.Json

Marten does need to know what the event types are before you issue queries against the event data (it's just to handle the deserialization from JSON). The event registration will happen automatically when you append events, but for production usage when you may be querying event data before you append anything, you just need to register the event types upfront like this:


var store2 = DocumentStore.For(_ =>
{
    _.Connection(ConnectionSource.ConnectionString);
    _.AutoCreateSchemaObjects = AutoCreate.None;

    _.Events.AddEventType(typeof(QuestStarted));
    _.Events.AddEventType(typeof(MonsterSlayed));
});

Stream or Aggregate Types

At this point there are no specific requirements about stream aggregate types as they are purely marker types. In the future we will probably support aggregating events via snapshot caching using the aggregate type.

Starting a new Stream

As of Marten v0.9, you can optionally start a new event stream against some kind of .Net type that theoretically marks the type of stream you're capturing. Marten does not yet use this type as anything more than metadata, but our thought is that some projections would key off this information and in a future version use that aggregate type to perform versioned snapshots of the entire stream. We may also make the aggregate type optional so that you could just supply either a string to mark the "stream type" or work without a stream type.

As usual, our sample problem domain is the Lord of the Rings style "Quest." For now, you can either start a new stream and let Marten assign the Guid id for the stream:


var joined = new MembersJoined { Members = new[] { "Rand", "Matt", "Perrin", "Thom" } };
var departed = new MembersDeparted { Members = new[] { "Thom" } };

var id = session.Events.StartStream(joined, departed).Id;
session.SaveChanges();

Or have Marten use a Guid value that you provide yourself:


var joined = new MembersJoined { Members = new[] { "Rand", "Matt", "Perrin", "Thom" } };
var departed = new MembersDeparted { Members = new[] { "Thom" } };

var id = Guid.NewGuid();
session.Events.StartStream(id, joined, departed);
session.SaveChanges();

For stream identity (strings vs. Guids), see Stream Identity.

Note that StartStream checks for and existing stream and throws ExistingStreamIdCollisionException if a matching stream already exists.

Appending Events

If you have an existing stream, you can later append additional events with IEventStore.Append() as shown below:


var joined = new MembersJoined { Members = new[] { "Rand", "Matt", "Perrin", "Thom" } };
var departed = new MembersDeparted { Members = new[] { "Thom" } };

session.Events.Append(id, joined, departed);

session.SaveChanges();

Appending & Assertions

IEventStore.Append() supports an overload taking in a parameter int expectedVersion that can be used to assert that events are inserted into the event stream if and only if the maximum event id for the stream matches the expected version after event insertions. Otherwise the transaction is aborted and an EventStreamUnexpectedMaxEventIdException exception is thrown.


session.Events.StartStream(id, started);
session.SaveChanges();

var joined = new MembersJoined { Members = new[] { "Rand", "Matt", "Perrin", "Thom" } };
var departed = new MembersDeparted { Members = new[] { "Thom" } };

// Events are appended into the stream only if the maximum event id for the stream
// would be 3 after the append operation.
session.Events.Append(id, 3, joined, departed);

session.SaveChanges();

StartStream vs. Append

Both StartStream and Append can be used to start a new event stream. The difference with the methods is that StartStream always checks for existing stream and throws ExistingStreamIdCollisionException in case the stream already exists.