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:
- Public, concrete types
- 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(_ =>
{
_.DatabaseSchemaName = "samples";
_.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 an 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.