Example of Event Sourcing in .NET Core
Install recent version of the Postgres DB (eg. from: https://www.postgresql.org/download/)
Video presentation (PL):
Slides (PL):
https://github.com/oskardudycz/EventSourcing.NetCore/blob/master/Slides.pptx
- Creating event store
- Event Stream - is a representation of the entity in event sourcing. It's a set of events that hapened for the entity with the exact id. Stream id should be unique, can have different types but usually is a Guid.
- Stream starting - stream should be always started with a unique id. Marten provides three ways of starting stream:
- calling StartStream method with a stream id
var streamId = Guid.NewGuid(); documentSession.Events.StartStream<TaskList>(streamId);
- calling StartStream method with a set of events
var @event = new TaskCreated { TaskId = Guid.NewGuid(), Description = "Description" }; var streamId = documentSession.Events.StartStream<TaskList>(@event);
- just appending events with a stream id
var @event = new TaskCreated { TaskId = Guid.NewGuid(), Description = "Description" }; var streamId = Guid.NewGuid(); documentSession.Events.Append(streamId, @event);
- calling StartStream method with a stream id
- Stream loading - all events that were placed on the event store should be possible to load them back. Marten allows to:
- get list of event by calling FetchStream method with a stream id
var eventsList = documentSession.Events.FetchStream(streamId);
- geting one event by its id
var @event = documentSession.Events.Load<TaskCreated>(eventId);
- get list of event by calling FetchStream method with a stream id
- Stream loading from exact state - all events that were placed on the event store should be possible to load them back. Marten allows to get stream from exact state by:
- timestamp (has to be in UTC)
var dateTime = new DateTime(2017, 1, 11); var events = documentSession.Events.FetchStream(streamId, timestamp: dateTime);
- version number
var versionNumber = 3; var events = documentSession.Events.FetchStream(streamId, version: versionNumber);
- timestamp (has to be in UTC)
- Stream starting - stream should be always started with a unique id. Marten provides three ways of starting stream:
- Event stream aggregation - events that were stored can be aggregated to form the entity once again. During aggregation process events are taken by the stream id and then replied event by event (so eg. NewTaskAdded, DescriptionOfTaskChanged, TaskRemoved). At first empty entity instance is being created (by calling default constructor). Then events based of the order of apperance (timestamp) are being applied on the entity instance by calling proper Apply methods.
- Aggregation general rules
- Online Aggregation - online aggregation is a process when entity instance is being constructed on the fly from events. Events are taken from the database and then aggregation is being done. The biggest advantage of the online aggregation is that it always gets the most recent business logic. So after the change it's automatically reflected and it's not needed to do any migration or updates.
- Inline Aggregation (Snapshot) - inline aggregation happens when we take the snapshot of the entity from the db. In that case it's not needed to get all events. Marten stores the snapshot as a document. This is good for the performance reasons, because only one record is being materialized. The con of using inline aggregation is that after business logic has changed records need to be reaggregated.
- Reaggregation - one of the biggest advantage of the event sourcing is flexibility to business logic updates. It's not needed to perform complex miggration. Online aggregation it's not needed to perform reaggregation in fact it's being made always automatically. Inline aggregation needs to be reaggregated. It can be done by performing online aggregation on all stream events and storing the result as a snapshot.
- reaggregation of inline snapshot with Marten
var onlineAggregation = documentSession.Events.AggregateStream<TEntity>(streamId); documentSession.Store<TEntity>(onlineAggregation); documentSession.SaveChanges();
- reaggregation of inline snapshot with Marten
- Event transformations
- Initialization - MediatR uses services locator pattern to find proper handler for message type.
- Sending Messages - finds and uses first registered handler for the message type. It could be used for queries (when we need to return values), commands (when we performing an action).
- No Handlers - when MediatR doesn't find proper handler it throws an exception.
- Synchronous Handler - by implementing implementing IRequestHandler we're making decision that this handler should be run one by one with other synchronous handlers.
- Aynchronous Handler - by implementing implementing IAsyncRequestHandler we're making decision that this handler should be run asynchronously with other async handlers (so we don't wait for the previous handler to finish its work).
- More Than One Handler - when there is more than one handler registered MediatR takes only one ignoring others when Send method is being called.
- Publishing Messages - finds and uses all registered handlers for the message type. It's good for processing events.
- No Handlers - when MediatR doesn't find proper handler it throws an exception
- Synchronous Handler - by implementing implementing INotificationHandler we're making decision that this handler should be run one by one with other synchronous handlers.
- Aynchronous Handler - by implementing implementing IAsyncNotificationHandler we're making decision that this handler should be run asynchronously with other async handlers (so we don't wait for the previous handler to finish its work)
- More Than One Handler - when there is more than one handler registered MediatR takes only all of them when calling Publish method
- Pipeline (to be defined)