Awesome
<img src="https://img.shields.io/badge/LinkedIn-0077B5?style=for-the-badge&logo=linkedin&logoColor=white" height="20px" />
EventSourcing .NET
Tutorial, practical samples and other resources about Event Sourcing in .NET. See also my similar repositories for JVM and NodeJS.
- EventSourcing .NET
- 1. Event Sourcing
- 2. Videos
- 2.1. Practical Event Sourcing with Marten
- 2.2. Keep your streams short! Or how to model event-sourced systems efficiently
- 2.3. Let's build event store in one hour!
- 2.4. CQRS is Simpler than you think with C#11 & NET7
- 2.5. Practical Introduction to Event Sourcing with EventStoreDB
- 2.6. How to deal with privacy and GDPR in Event-Sourced systems
- 2.7 Let's build the worst Event Sourcing system!
- 2.8 The Light and The Dark Side of the Event-Driven Design
- 2.9 Implementing Distributed Processes
- 2.10 Conversation with Yves Lorphelin about CQRS
- 2.11. Never Lose Data Again - Event Sourcing to the Rescue!
- 3. Support
- 4. Prerequisites
- 5. Tools used
- 6. Samples
- 6.1 Pragmatic Event Sourcing With Marten
- 6.2 ECommerce with Marten
- 6.3 Simple EventSourcing with EventStoreDB
- 6.4 Implementing Distributed Processes
- 6.5 ECommerce with EventStoreDB
- 6.6 Warehouse
- 6.7 Warehouse Minimal API
- 6.8 Event Versioning
- 6.9 Event Pipelines
- 6.10 Meetings Management with Marten
- 6.11 Cinema Tickets Reservations with Marten
- 6.12 SmartHome IoT with Marten
- 7. Self-paced training Kits
- 8. Articles
- 9. Event Store - Marten
- 10. CQRS (Command Query Responsibility Separation)
- 11. NuGet packages to help you get started.
- 12. Other resources
- 12.1 Introduction
- 12.2 Event Sourcing on production
- 12.3 Projections
- 12.4 Snapshots
- 12.5 Versioning
- 12.6 Storage
- 12.7 Design & Modeling
- 12.8 GDPR
- 12.9 Conflict Detection
- 12.10 Functional programming
- 12.12 Testing
- 12.13 CQRS
- 12.14 Tools
- 12.15 Event processing
- 12.16 Distributed processes
- 12.17 Domain Driven Design
- 12.18 Whitepapers
- 12.19 Event Sourcing Concerns
- 12.20 This is NOT Event Sourcing (but Event Streaming)
- 12.21 Architecture Weekly
- License
1. Event Sourcing
1.1 What is Event Sourcing?
Event Sourcing is a design pattern in which results of business operations are stored as a series of events.
It is an alternative way to persist data. In contrast with state-oriented persistence that only keeps the latest version of the entity state, Event Sourcing stores each state change as a separate event.
Thanks to that, no business data is lost. Each operation results in the event stored in the database. That enables extended auditing and diagnostics capabilities (both technically and business-wise). What's more, as events contains the business context, it allows wide business analysis and reporting.
In this repository I'm showing different aspects and patterns around Event Sourcing from the basic to advanced practices.
Read more in my articles:
1.2 What is Event?
Events represent facts in the past. They carry information about something accomplished. It should be named in the past tense, e.g. "user added", "order confirmed". Events are not directed to a specific recipient - they're broadcasted information. It's like telling a story at a party. We hope that someone listens to us, but we may quickly realise that no one is paying attention.
Events:
- are immutable: "What has been seen, cannot be unseen".
- can be ignored but cannot be retracted (as you cannot change the past).
- can be interpreted differently. The basketball match result is a fact. Winning team fans will interpret it positively. Losing team fans - not so much.
Read more in my articles:
- π What's the difference between a command and an event?
- π Events should be as small as possible, right?
- π Anti-patterns in event modelling - Property Sourcing
- π Anti-patterns in event modelling - State Obsession
1.3 What is Stream?
Events are logically grouped into streams. In Event Sourcing, streams are the representation of the entities. All the entity state mutations end up as the persisted events. Entity state is retrieved by reading all the stream events and applying them one by one in the order of appearance.
A stream should have a unique identifier representing the specific object. Each event has its own unique position within a stream. This position is usually represented by a numeric, incremental value. This number can be used to define the order of the events while retrieving the state. It can also be used to detect concurrency issues.
1.4 Event representation
Technically events are messages.
They may be represented, e.g. in JSON, Binary, XML format. Besides the data, they usually contain:
- id: unique event identifier.
- type: name of the event, e.g. "invoice issued".
- stream id: object id for which event was registered (e.g. invoice id).
- stream position (also named version, order of occurrence, etc.): the number used to decide the order of the event's occurrence for the specific object (stream).
- timestamp: representing a time at which the event happened.
- other metadata like
correlation id
,causation id
, etc.
Sample event JSON can look like:
{
"id": "e44f813c-1a2f-4747-aed5-086805c6450e",
"type": "invoice-issued",
"streamId": "INV/2021/11/01",
"streamPosition": 1,
"timestamp": "2021-11-01T00:05:32.000Z",
"data":
{
"issuedTo": {
"name": "Oscar the Grouch",
"address": "123 Sesame Street"
},
"amount": 34.12,
"number": "INV/2021/11/01",
"issuedAt": "2021-11-01T00:05:32.000Z"
},
"metadata":
{
"correlationId": "1fecc92e-3197-4191-b929-bd306e1110a4",
"causationId": "c3cf07e8-9f2f-4c2d-a8e9-f8a612b4a7f1"
}
}
Read more in my articles:
1.5 Event Storage
Event Sourcing is not related to any type of storage implementation. As long as it fulfills the assumptions, it can be implemented having any backing database (relational, document, etc.). The state has to be represented by the append-only log of events. The events are stored in chronological order, and new events are appended to the previous event. Event Stores are the databases' category explicitly designed for such purpose.
Read more in my articles:
- π Let's build event store in one hour!
- π What if I told you that Relational Databases are in fact Event Stores?
1.6 Retrieving the current state from events
In Event Sourcing, the state is stored in events. Events are logically grouped into streams. Streams can be thought of as the entities' representation. Traditionally (e.g. in relational or document approach), each entity is stored as a separate record.
Id | IssuerName | IssuerAddress | Amount | Number | IssuedAt |
---|---|---|---|---|---|
e44f813c | Oscar the Grouch | 123 Sesame Street | 34.12 | INV/2021/11/01 | 2021-11-01 |
In Event Sourcing, the entity is stored as the series of events that happened for this specific object, e.g. InvoiceInitiated
, InvoiceIssued
, InvoiceSent
.
[
{
"id": "e44f813c-1a2f-4747-aed5-086805c6450e",
"type": "invoice-initiated",
"streamId": "INV/2021/11/01",
"streamPosition": 1,
"timestamp": "2021-11-01T00:05:32.000Z",
"data":
{
"issuer": {
"name": "Oscar the Grouch",
"address": "123 Sesame Street",
},
"amount": 34.12,
"number": "INV/2021/11/01",
"initiatedAt": "2021-11-01T00:05:32.000Z"
}
},
{
"id": "5421d67d-d0fe-4c4c-b232-ff284810fb59",
"type": "invoice-issued",
"streamId": "INV/2021/11/01",
"streamPosition": 2,
"timestamp": "2021-11-01T00:11:32.000Z",
"data":
{
"issuedTo": "Cookie Monster",
"issuedAt": "2021-11-01T00:11:32.000Z"
}
},
{
"id": "637cfe0f-ed38-4595-8b17-2534cc706abf",
"type": "invoice-sent",
"streamId": "INV/2021/11/01",
"streamPosition": 3,
"timestamp": "2021-11-01T00:12:01.000Z",
"data":
{
"sentVia": "email",
"sentAt": "2021-11-01T00:12:01.000Z"
}
}
]
All of those events share the stream id ("streamId": "INV/2021/11/01"
), and have incremented stream positions.
In Event Sourcing each entity is represented by its stream: the sequence of events correlated by the stream id ordered by stream position.
To get the current state of an entity we need to perform the stream aggregation process. We're translating the set of events into a single entity. This can be done with the following steps:
- Read all events for the specific stream.
- Order them ascending in the order of appearance (by the event's stream position).
- Construct the empty object of the entity type (e.g. with default constructor).
- Apply each event on the entity.
This process is called also stream aggregation or state rehydration.
We could implement that as:
public record Person(
string Name,
string Address
);
public record InvoiceInitiated(
double Amount,
string Number,
Person IssuedTo,
DateTime InitiatedAt
);
public record InvoiceIssued(
string IssuedBy,
DateTime IssuedAt
);
public enum InvoiceSendMethod
{
Email,
Post
}
public record InvoiceSent(
InvoiceSendMethod SentVia,
DateTime SentAt
);
public enum InvoiceStatus
{
Initiated = 1,
Issued = 2,
Sent = 3
}
public class Invoice
{
public string Id { get;set; }
public double Amount { get; private set; }
public string Number { get; private set; }
public InvoiceStatus Status { get; private set; }
public Person IssuedTo { get; private set; }
public DateTime InitiatedAt { get; private set; }
public string IssuedBy { get; private set; }
public DateTime IssuedAt { get; private set; }
public InvoiceSendMethod SentVia { get; private set; }
public DateTime SentAt { get; private set; }
public void Evolve(object @event)
{
switch (@event)
{
case InvoiceInitiated invoiceInitiated:
Apply(invoiceInitiated);
break;
case InvoiceIssued invoiceIssued:
Apply(invoiceIssued);
break;
case InvoiceSent invoiceSent:
Apply(invoiceSent);
break;
}
}
private void Apply(InvoiceInitiated @event)
{
Id = @event.Number;
Amount = @event.Amount;
Number = @event.Number;
IssuedTo = @event.IssuedTo;
InitiatedAt = @event.InitiatedAt;
Status = InvoiceStatus.Initiated;
}
private void Apply(InvoiceIssued @event)
{
IssuedBy = @event.IssuedBy;
IssuedAt = @event.IssuedAt;
Status = InvoiceStatus.Issued;
}
private void Apply(InvoiceSent @event)
{
SentVia = @event.SentVia;
SentAt = @event.SentAt;
Status = InvoiceStatus.Sent;
}
}
and use it as:
var invoiceInitiated = new InvoiceInitiated(
34.12,
"INV/2021/11/01",
new Person("Oscar the Grouch", "123 Sesame Street"),
DateTime.UtcNow
);
var invoiceIssued = new InvoiceIssued(
"Cookie Monster",
DateTime.UtcNow
);
var invoiceSent = new InvoiceSent(
InvoiceSendMethod.Email,
DateTime.UtcNow
);
// 1,2. Get all events and sort them in the order of appearance
var events = new object[] {invoiceInitiated, invoiceIssued, invoiceSent};
// 3. Construct empty Invoice object
var invoice = new Invoice();
// 4. Apply each event on the entity.
foreach (var @event in events)
{
invoice.Evolve(@event);
}
and generalise this into Aggregate
base class:
public abstract class Aggregate<T>
{
public T Id { get; protected set; }
protected Aggregate() { }
public virtual void Evolve(object @event) { }
}
The biggest advantage of "online" stream aggregation is that it always uses the most recent business logic. So after the change in the apply method, it's automatically reflected on the next run. If events data is fine, then it's not needed to do any migration or updates.
In Marten Evolve
method is not needed. Marten uses naming convention and call the Apply
method internally. It has to:
- have single parameter with event object,
- have
void
type as the result.
See samples:
Read more in my article:
- π How to get the current entity state from events?
- π Should you throw an exception when rebuilding the state from events?
1.7 Strongly-Typed ids with Marten
Strongly typed ids (or, in general, a proper type system) can make your code more predictable. It reduces the chance of trivial mistakes, like accidentally changing parameters order of the same primitive type.
So for such code:
var reservationId = "RES/01";
var seatId = "SEAT/22";
var customerId = "CUS/291";
var reservation = new Reservation(
reservationId,
seatId,
customerId
);
the compiler won't catch if you switch reservationId
with seatId
.
If you use strongly typed ids, then compile will catch that issue:
var reservationId = new ReservationId("RES/01");
var seatId = new SeatId("SEAT/22");
var customerId = new CustomerId("CUS/291");
var reservation = new Reservation(
reservationId,
seatId,
customerId
);
They're not ideal, as they're usually not playing well with the storage engines. Typical issues are: serialisation, Linq queries, etc. For some cases they may be just overkill. You need to pick your poison.
To reduce tedious, copy/paste code, it's worth defining a strongly-typed id base class, like:
public class StronglyTypedValue<T>: IEquatable<StronglyTypedValue<T>> where T: IComparable<T>
{
public T Value { get; }
public StronglyTypedValue(T value)
{
Value = value;
}
public bool Equals(StronglyTypedValue<T>? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return EqualityComparer<T>.Default.Equals(Value, other.Value);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((StronglyTypedValue<T>)obj);
}
public override int GetHashCode()
{
return EqualityComparer<T>.Default.GetHashCode(Value);
}
public static bool operator ==(StronglyTypedValue<T>? left, StronglyTypedValue<T>? right)
{
return Equals(left, right);
}
public static bool operator !=(StronglyTypedValue<T>? left, StronglyTypedValue<T>? right)
{
return !Equals(left, right);
}
}
Then you can define specific id class as:
public class ReservationId: StronglyTypedValue<Guid>
{
public ReservationId(Guid value) : base(value)
{
}
}
You can even add additional rules:
public class ReservationNumber: StronglyTypedValue<string>
{
public ReservationNumber(string value) : base(value)
{
if (string.IsNullOrEmpty(value) || !value.StartsWith("RES/") || value.Length <= 4)
throw new ArgumentOutOfRangeException(nameof(value));
}
}
The base class working with Marten, can be defined as:
public abstract class Aggregate<TKey, T>
where TKey: StronglyTypedValue<T>
where T : IComparable<T>
{
public TKey Id { get; set; } = default!;
[Identity]
public T AggregateId {
get => Id.Value;
set {}
}
public int Version { get; protected set; }
[JsonIgnore] private readonly Queue<object> uncommittedEvents = new();
public object[] DequeueUncommittedEvents()
{
var dequeuedEvents = uncommittedEvents.ToArray();
uncommittedEvents.Clear();
return dequeuedEvents;
}
protected void Enqueue(object @event)
{
uncommittedEvents.Enqueue(@event);
}
}
Marten requires the id with public setter and getter of string
or Guid
. We used the trick and added AggregateId
with a strongly-typed backing field. We also informed Marten of the Identity attribute to use this field in its internals.
Example aggregate can look like:
public class Reservation : Aggregate<ReservationId, Guid>
{
public CustomerId CustomerId { get; private set; } = default!;
public SeatId SeatId { get; private set; } = default!;
public ReservationNumber Number { get; private set; } = default!;
public ReservationStatus Status { get; private set; }
public static Reservation CreateTentative(
SeatId seatId,
CustomerId customerId)
{
return new Reservation(
new ReservationId(Guid.NewGuid()),
seatId,
customerId,
new ReservationNumber(Guid.NewGuid().ToString())
);
}
// (...)
}
See the full sample here.
Read more in the article:
- π Using strongly-typed identifiers with Marten
- π Immutable Value Objects are simpler and more useful than you think!
2. Videos
2.1. Practical Event Sourcing with Marten
<a href="https://www.youtube.com/watch?v=jnDchr5eabI&list=PLw-VZz_H4iiqUeEBDfGNendS0B3qIk-ps&index=1" target="_blank"><img src="https://img.youtube.com/vi/jnDchr5eabI/0.jpg" alt="Pragmatic Event Sourcing with Marten" width="640" height="480" border="10" /></a>
2.2. Keep your streams short! Or how to model event-sourced systems efficiently
<a href="https://www.youtube.com/watch?v=gG6DGmYKk4I&list=PLw-VZz_H4iiqUeEBDfGNendS0B3qIk-ps&index=2" target="_blank"><img src="https://img.youtube.com/vi/gG6DGmYKk4I/0.jpg" alt="Keep your streams short! Or how to model event-sourced systems efficiently" width="640" height="480" border="10" /></a>
2.3. Let's build event store in one hour!
<a href="https://www.youtube.com/watch?v=gaoZdtQSOTo&list=PLw-VZz_H4iiqUeEBDfGNendS0B3qIk-ps&index=2" target="_blank"><img src="https://img.youtube.com/vi/gaoZdtQSOTo/0.jpg" alt="Let's build event store in one hour!" width="640" height="480" border="10" /></a>
2.4. CQRS is Simpler than you think with C#11 & NET7
<a href="https://www.youtube.com/watch?v=iY7LO289qnQ" target="_blank"><img src="https://img.youtube.com/vi/iY7LO289qnQ/0.jpg" alt="CQRS is Simpler than you think with C#11 & NET7" width="640" height="480" border="10" /></a>
2.5. Practical Introduction to Event Sourcing with EventStoreDB
<a href="https://www.youtube.com/watch?v=rqYPVzjoxqI" target="_blank"><img src="https://img.youtube.com/vi/rqYPVzjoxqI/0.jpg" alt="Practical introduction to Event Sourcing with EventStoreDB" width="640" height="480" border="10" /></a>
2.6. How to deal with privacy and GDPR in Event-Sourced systems
<a href="https://www.youtube.com/watch?v=CI7JPFLlpBw" target="_blank"><img src="https://img.youtube.com/vi/CI7JPFLlpBw/0.jpg" alt="How to deal with privacy and GDPR in Event-Sourced systems" width="640" height="480" border="10" /></a>
2.7 Let's build the worst Event Sourcing system!
<a href="https://www.youtube.com/watch?v=Lu-skMQ-vAw" target="_blank"><img src="https://img.youtube.com/vi/Lu-skMQ-vAw/0.jpg" alt="Let's build the worst Event Sourcing system!" width="640" height="480" border="10" /></a>
2.8 The Light and The Dark Side of the Event-Driven Design
<a href="https://www.youtube.com/watch?v=ZGugOiYcq8k" target="_blank"><img src="https://img.youtube.com/vi/ZGugOiYcq8k/0.jpg" alt="The Light and The Dark Side of the Event-Driven Design" width="640" height="480" border="10" /></a>
2.9 Implementing Distributed Processes
<a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed" target="_blank"><img src="https://substackcdn.com/image/fetch/w_1920,h_1080,c_fill,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-video.s3.amazonaws.com%2Fvideo_upload%2Fpost%2F69413446%2F526b9100-7271-4482-99e7-9559416e9848%2Ftranscoded-00624.png" alt="Implementing Distributed Processes" width="320" border="10" /></a>
2.10 Conversation with Yves Lorphelin about CQRS
<a href="https://www.youtube.com/watch?v=D-3N2vQ7ADE" target="_blank"><img src="https://img.youtube.com/vi/D-3N2vQ7ADE/0.jpg" alt="Event Store Conversations: Yves Lorphelin talks to Oskar Dudycz about CQRS (EN)" width="640" height="480" border="10" /></a>
2.11. Never Lose Data Again - Event Sourcing to the Rescue!
<a href="https://www.youtube.com/watch?v=fDC465jJoDk" target="_blank"><img src="https://img.youtube.com/vi/fDC465jJoDk/0.jpg" alt="Never Lose Data Again - Event Sourcing to the Rescue!" width="640" height="480" border="10" /></a>
3. Support
Feel free to create an issue if you have any questions or request for more explanation or samples. I also take Pull Requests!
π If this repository helped you - I'd be more than happy if you join the group of my official supporters at:
π Github Sponsors
β Star on GitHub or sharing with your friends will also help!
4. Prerequisites
For running the Event Store examples you need to have:
- .NET 6 installed - https://dotnet.microsoft.com/download/dotnet/6.0
- Docker installed. Then going to the
docker
folder and running:
docker compose --profile all up
More information about using .NET, WebApi and Docker you can find in my other tutorials: WebApi with .NET
5. Tools used
- Marten - Event Store and Read Models
- EventStoreDB - Event Store
- Kafka - External Durable Message Bus to integrate services
- ElasticSearch - Read Models
6. Samples
See also fully working, real-world samples of Event Sourcing and CQRS applications in Samples folder.
Samples are using CQRS architecture. They're sliced based on the business modules and operations. Read more about the assumptions in "How to slice the codebase effectively?".
6.1 Pragmatic Event Sourcing With Marten
- Simplest CQRS and Event Sourcing flow using Minimal API,
- Cutting the number of layers and boilerplate complex code to bare minimum,
- Using all Marten helpers like
WriteToAggregate
,AggregateStream
to simplify the processing, - Examples of all the typical Marten's projections,
- Example of how and where to use C# Records, Nullable Reference Types, etc,
- No Aggregates. Commands are handled in the domain service as pure functions.
6.2 ECommerce with Marten
- typical Event Sourcing and CQRS flow,
- DDD using Aggregates,
- microservices example,
- stores events to Marten,
- distributed processes coordinated by Saga (Order Saga),
- Kafka as a messaging platform to integrate microservices,
- example of the case when some services are event-sourced (Carts, Orders, Payments) and some are not (Shipments using EntityFramework as ORM)
6.3 Simple EventSourcing with EventStoreDB
- typical Event Sourcing and CQRS flow,
- functional composition, no aggregates, just data and functions,
- stores events to EventStoreDB,
- Builds read models using Subscription to
$all
, - Read models are stored as Postgres tables using EntityFramework.
6.4 Implementing Distributed Processes
- orchestrate and coordinate business workflow spanning across multiple aggregates using Saga pattern,
- handle distributed processing both for asynchronous commands scheduling and events publishing,
- getting at-least-once delivery guarantee,
- implementing command store and outbox pattern on top of Marten and EventStoreDB,
- unit testing aggregates and Saga with a little help from Ogooreck,
- testing asynchronous code.
6.5 ECommerce with EventStoreDB
- typical Event Sourcing and CQRS flow,
- DDD using Aggregates,
- stores events to EventStoreDB,
- Builds read models using Subscription to
$all
. - Read models are stored as Marten documents.
6.6 Warehouse
- simplest CQRS flow using .NET Endpoints,
- example of how and where to use C# Records, Nullable Reference Types, etc,
- No Event Sourcing! Using Entity Framework to show that CQRS is not bounded to Event Sourcing or any type of storage,
- No Aggregates! CQRS do not need DDD. Business logic can be handled in handlers.
6.7 Warehouse Minimal API
Variation of the previous example, but:
- using Minimal API,
- example how to inject handlers in MediatR like style to decouple API from handlers.
- π Read more CQRS is simpler than you think with .NET 6 and C# 10
6.8 Event Versioning
Shows how to handle basic event schema versioning scenarios using event and stream transformations (e.g. upcasting):
- Simple mapping
- Upcasting
- Downcasters
- Events Transformations
- Stream Transformation
- Summary
- π Simple patterns for events schema versioning
6.9 Event Pipelines
Shows how to compose event handlers in the processing pipelines to:
- filter events,
- transform them,
- NOT requiring marker interfaces for events,
- NOT requiring marker interfaces for handlers,
- enables composition through regular functions,
- allows using interfaces and classes if you want to,
- can be used with Dependency Injection, but also without through builder,
- integrates with MediatR if you want to.
- π Read more How to build a simple event pipeline
6.10 Meetings Management with Marten
- typical Event Sourcing and CQRS flow,
- DDD using Aggregates,
- microservices example,
- stores events to Marten,
- Kafka as a messaging platform to integrate microservices,
- read models handled in separate microservice and stored to other database (ElasticSearch)
6.11 Cinema Tickets Reservations with Marten
- typical Event Sourcing and CQRS flow,
- DDD using Aggregates,
- stores events to Marten.
6.12 SmartHome IoT with Marten
- typical Event Sourcing and CQRS flow,
- DDD using Aggregates,
- stores events to Marten,
- asynchronous projections rebuild using AsyncDaemon feature.
7. Self-paced training Kits
I prepared the self-paced training Kits for the Event Sourcing. See more in the Workshop description.
7.1 Introduction to Event Sourcing
Event Sourcing is perceived as a complex pattern. Some believe that it's like Nessie, everyone's heard about it, but rarely seen it. In fact, Event Sourcing is a pretty practical and straightforward concept. It helps build predictable applications closer to business. Nowadays, storage is cheap, and information is priceless. In Event Sourcing, no data is lost.
The workshop aims to build the knowledge of the general concept and its related patterns for the participants. The acquired knowledge will allow for the conscious design of architectural solutions and the analysis of associated risks.
The emphasis will be on a pragmatic understanding of architectures and applying it in practice using Marten and EventStoreDB.
You can do the workshop as a self-paced kit. That should give you a good foundation for starting your journey with Event Sourcing and learning tools like Marten and EventStoreDB. If you'd like to get full coverage with all nuances of the private workshop, feel free to contact me via email.
- Events definition.
- Getting State from events.
- Appending Events:
- Getting State from events
- Business logic:
- Optimistic Concurrency:
- Projections:
7.2 Build your own Event Store
It teaches the event store basics by showing how to build your Event Store on top of Relational Database. It starts with the tables setup, goes through appending events, aggregations, projections, snapshots, and finishes with the Marten
basics.
- Streams Table
- Events Table
- Appending Events
- Optimistic Concurrency Handling
- Event Store Methods
- Stream Aggregation
- Time Travelling
- Aggregate and Repositories
- Snapshots
- Projections
- Projections With Marten
8. Articles
Read also more on the Event Sourcing and CQRS topics in my blog posts:
- π Introduction to Event Sourcing - Self Paced Kit
- π Never Lose Data Again - Event Sourcing to the Rescue!
- π Event stores are key-value databases, and why that matters
- π What's the difference between a command and an event?
- π Event Streaming is not Event Sourcing!
- π Don't let Event-Driven Architecture buzzwords fool you
- π Events should be as small as possible, right?
- π How to get the current entity state from events?
- π Should you throw an exception when rebuilding the state from events?
- π Let's build event store in one hour!
- π How to effectively compose your business logic
- π Slim your aggregates with Event Sourcing!
- π Testing business logic in Event Sourcing, and beyond!
- π Writing and testing business logic in F#
- π How to ensure uniqueness in Event Sourcing
- π Ensuring uniqueness in Marten event store
- π Anti-patterns in event modelling - Property Sourcing
- π Anti-patterns in event modelling - State Obsession
- π Why a bank account is not the best example of Event Sourcing?
- π When not to use Event Sourcing?
- π CQRS facts and myths explained
- π How to slice the codebase effectively?
- π Generic does not mean Simple?
- π Can command return a value?
- π CQRS is simpler than you think with .NET 6 and C# 10
- π Union types in C#
- π How to register all CQRS handlers by convention
- π How to use ETag header for optimistic concurrency
- π Guide to Projections and Read Models in Event-Driven Architecture
- π Event-driven projections in Marten explained
- π Projecting Marten events to Elasticsearch
- π Publishing read model changes from Marten
- π Integrating Marten with other systems
- π Persistent vs catch-up, EventStoreDB subscriptions in action
- π How to create projections of events for nested object structures?
- π How to scale projections in the event-driven systems?
- π Dealing with Eventual Consistency and Idempotency in MongoDB projections
- π How to test event-driven projections
- π Long-polling, how to make our async API synchronous
- π A simple trick for idempotency handling in the Elastic Search read model
- π How to do snapshots in Marten?
- π How events can help in making the state-based approach efficient
- π How to build a simple event pipeline
- π How to handle multiple commands in the same transaction
- π Mapping event type by convention
- π Explicit events serialisation in Event Sourcing
- π How to (not) do the events versioning?
- π Event Versioning with Marten
- π Simple patterns for events schema versioning
- π Set up OpenTelemetry with Event Sourcing and Marten
- π Immutable Value Objects are simpler and more useful than you think!
- π Explicit validation in C# just got simpler!
- π Notes about C# records and Nullable Reference Types
- π Using strongly-typed identifiers with Marten
- π How using events helps in a teams' autonomy
- π What texting your Ex has to do with Event-Driven Design?
- π What if I told you that Relational Databases are in fact Event Stores?
- π Optimistic concurrency for pessimistic times
- π Outbox, Inbox patterns and delivery guarantees explained
- π Saga and Process Manager - distributed processes in practice
- π Event-driven distributed processes by example
- π Testing asynchronous processes with a little help from .NET Channels
9. Event Store - Marten
- Creating event store
- Event Stream - is a representation of the entity in event sourcing. It's a set of events that happened 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 the stream:
- calling StartStream method with a stream id
var streamId = Guid.NewGuid(); documentSession.Events.StartStream<IssuesList>(streamId);
- calling StartStream method with a set of events
var @event = new IssueCreated { IssueId = Guid.NewGuid(), Description = "Description" }; var streamId = documentSession.Events.StartStream<IssuesList>(@event);
- just appending events with a stream id
var @event = new IssueCreated { IssueId = 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<IssueCreated>(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 the stream:
- Event stream aggregation - events that were stored can be aggregated to form the entity once again. During the aggregation, process events are taken by the stream id and then replayed event by event (so eg. NewTaskAdded, DescriptionOfTaskChanged, TaskRemoved). At first, an empty entity instance is being created (by calling default constructor). Then events based on the order of appearance are being applied on the entity instance by calling proper Apply methods.
- 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 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 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 advantages of the event sourcing is flexibility to business logic updates. It's not needed to perform complex migration. For online aggregation it's not needed to perform reaggregation - it's being made always automatically. The 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
- Events projection
- Multitenancy per schema
10. CQRS (Command Query Responsibility Separation)
11. NuGet packages to help you get started.
I gathered and generalized all of the practices used in this tutorial/samples in Nuget Packages maintained by me GoldenEye Framework. See more in:
-
GoldenEye DDD package - it provides a set of base and bootstrap classes that helps you to reduce boilerplate code and help you focus on writing business code. You can find all classes like Commands/Queries/Event handlers and many more. To use it run:
dotnet add package GoldenEye
-
GoldenEye Marten package - contains helpers, and abstractions to use Marten as document/event store. Gives you abstractions like repositories etc. To use it run:
dotnet add package GoldenEye.Marten
12. Other resources
12.1 Introduction
- π Event Store - A Beginner's Guide to Event Sourcing
- π Greg Young - CQRS & Event Sourcing
- π° Lorenzo Nicora - A visual introduction to event sourcing and cqrs
- π Mathew McLoughlin - An Introduction to CQRS and Event Sourcing Patterns
- π Emily Stamey - Hey Boss, Event Sourcing Could Fix That!
- π Derek Comartin - Event Sourcing Example & Explained in plain English
- π Duncan Jones - Introduction to event sourcing and CQRS
- π Roman Sachse - Event Sourcing - Do it yourself series
- π Jay Kreps - Why local state is a fundamental primitive in stream processing
- π Jay Kreps - The Log: What every software engineer should know about real-time data's unifying abstraction
- π Duncan Jones - Event Sourcing and CQRS on Azure serverless functions
- π Christian Stettler - Domain Events vs. Event Sourcing
- π Martin Fowler - The Many Meanings of Event-Driven Architecture
- π Martin Fowler - Event Sourcing
- π Dennis Doomen - 16 design guidelines for successful Event Sourcing
- π Martin Kleppmann - Event Sourcing and Stream Processing at Scale
- π Dennis Doomen - The Good, The Bad and the Ugly of Event Sourcing
- π Alexey Zimarev - DDD, Event Sourcing and Actors
- π Thomas BΓΈgh Fangel - Event Sourcing: Traceability, Consistency, Correctness
- π Joseph Choe - Event Sourcing, Part 1: User Registration
- π Steven Van Beelen - Intro to Event-Driven Microservices using DDD, CQRS & Event sourcing
- π Yves Lorphelin - The Inevitable Event-Centric Book
- π Microsoft - Exploring CQRS and Event Sourcing
<a href='#event-sourcing-on-production' id='event-sourcing-on-production' class='anchor' aria-hidden='true'></a>
12.2 Event Sourcing on production
- π Alexey Zimarev - Event Sourcing in Production
- π Leo Gorodinski - Scaling Event-Sourcing at Jet
- π EventStoreDB - Customers' case studies
- π P. Avery, R. Reta - Scaling Event Sourcing for Netflix Downloads
- π Netflix - Scaling Event Sourcing for Netflix Downloads, Episode 1
- π Netflix - Scaling Event Sourcing for Netflix Downloads, Episode 2
- π M. Overeem, M. Spoor, S. Jansen, S. Brinkkemper - An Empirical Characterization of Event Sourced Systems and Their Schema Evolution -- Lessons from Industry
- π Michiel Overeem - Event Sourcing after launch
- π Greg Young - A Decade of DDD, CQRS, Event Sourcing
- π M. Kadijk, J. Taal - The beautiful headache called event sourcing
- π Thomas Weiss - Planet-scale event sourcing with Azure Cosmos DB
- π D. Kuzenski, N. Piani - Beyond APIs: Re-architected System Integrations as Event Sourced
- π Greg Young - Why Event Sourced Systems Fail
- π Joris Kuipers - Day 2 problems in CQRS and event sourcing
- π Kacper Gunia - War Story: How a Large Corporation Used DDD to Replace a Loyalty System
- π Vladik Khononov - The Dark Side of Events
- π Pedro Costa - Migrating to Microservices and Event-Sourcing: the Dos and Dont's
- π Dennis Doomen - An Event Sourcing Retrospective - The Good, The Bad and the Ugly
- π David Schmitz - Event Sourcing You are doing it wrong
- π Dennis Doomen - A recipe for gradually migrating from CRUD to Event Sourcing
- π Nat Pryce - Mistakes made adopting event sourcing (and how we recovered)
12.3 Projections
- π Alexey Zimarev - Projections in Event Sourcing
- π Derek Comartin - Projections in Event Sourcing: Build ANY model you want!
- π Alexey Zimarev - Understanding read models
- π Kacper Gunia - Event Sourcing: Projections
- π Kacper Gunia - Event Sourcing Projections patterns: Deduplication strategies
- π Kacper Gunia - Event Sourcing Projections patterns: Consumer scaling
- π Kacper Gunia - Event Sourcing Projections patterns: Side effect handling
- π Kacper Gunia - Event Sourcing patterns: Replay side effect handling
- π Anton StΓΆckl - Live Projections for Read Models with Event Sourcing and CQRS
12.4 Snapshots
- π Kacper Gunia - Event Sourcing: Snapshotting
- π Derek Comartin - Event Sourcing: Rehydrating Aggregates with Snapshots
12.5 Versioning
- π Greg Young - Versioning in an Event Sourced System
- π Kacper Gunia - Event Sourcing: Snapshotting
- π M. Overeem, M. Spoor - The dark side of event sourcing: Managing data conversion
- π Savvas Kleanthous - Event immutability and dealing with change
- π Versioning in an Event Sourced System
12.6 Storage
- π Greg Young - Building an Event Storage
- π Yves Lorphelin - Requirements for the storage of events,
- π Anton StΓΆckl - Essential features of an Event Store for Event Sourcing
- π Adam Warski - Implementing event sourcing using a relational database
- π Greg Young - How an EventStore actually works
- π Andrii Litvinov - Event driven systems backed by MongoDB
- π Dave Remy - Turning the database inside out with Event Store
- π AWS Architecture Blog - How The Mill Adventure Implemented Event Sourcing at Scale Using DynamoDB
- π Sander Molenkamp: Practical CQRS and Event Sourcing on Azure
12.7 Design & Modeling
- π Mathias Verraes - DDD and Messaging Architectures
- π David Boike - Putting your events on a diet
- π Thomas Pierrain - As Time Goes Byβ¦ (a Bi-temporal Event Sourcing story)
- π Thomas Ploch - The One Question To Haunt Everyone: What is a DDD Aggregate?
- π Vaughn Vernon - Effective Aggregate Design Part I: Modeling a Single Aggregate
- π Derek Comartin - Aggregate (Root) Design: Separate Behavior & Data for Persistence
- π Mauro Servienti - All our aggregates are wrong
- π Microsoft - Domain events: design and implementation
- π Event Storming
- π Event Modeling
- π Wojciech SuwaΕa - Building Microservices On .NET Core β Part 5 Marten An Ideal Repository For Your Domain Aggregates
12.8 GDPR
- π Michiel Rook - Forget me please? Event sourcing and the GDPR
- π Michiel Rook - Event sourcing and the GDPR: a follow-up
- π Johan Sydseter - GDPR compliant event sourcing with HashiCorp Vault
- π Diego Martin - Protecting Sensitive Data in Event-Sourced Systems with Crypto Shredding
- π Bram Leenders - Scalable User Privacy: Crypto Shredding at Spotify
- π Stuart Herbert - Event Sourcing and GDPR: When Immutability Meets Reality
- π Masih Derkani - GDPR Compliance: Transparent Handing of Personally Identifiable Information in Event-Driven Systems
12.9 Conflict Detection
- π James Geall - Conflict Detection and Resolution in an EventSourced System
- π Lightbend - Data modelling for Replicated Event Sourcing
- π° Bartosz Sypytkowski - Collaborative Event Sourcing
12.10 Functional programming
12.12 Testing
12.13 CQRS
- π Greg Young - CQRS
- π Jimmy Bogard - CQRS and REST: the perfect match
- π Mark Seemann - CQS versus server-generated IDs
- π Julie Lerman - Data Points - CQRS and EF Data Models
- π Marco BΓΌrckel - Some thoughts on using CQRS without Event Sourcing
- π Bertrand Meyer - Eiffel: a language for software engineering (CQRS introduced)
- π Udi Dahan - CQRS β but different
- π Greg Young - CQRS, Task Based UIs, Event Sourcing agh!
12.14 Tools
- π οΈ Marten - .NET Transactional Document DB and Event Store on PostgreSQL
- π οΈ EventStoreDB - The stream database built for Event Sourcing
- π οΈ GoldenEye - The CQRS flavoured framework that will speed up your WebAPI and Microservices development
- π οΈ Eventuous - Event Sourcing for .NET
- π οΈ SQLStreamStore - Stream Store library targeting RDBMS based implementations for .NET
- π οΈ Equinox - .NET Event Sourcing library with CosmosDB, EventStoreDB, SqlStreamStore and integration test backends
12.15 Event processing
- π Kamil Grzybek - The Outbox Pattern
- π Dev Mentors - Inbox & Outbox pattern - transactional message processing
- π Jeremy D. Miller - Jasper's "Outbox" Pattern Support
- π Gunnar Morling - Reliable Microservices Data Exchange With the Outbox Pattern
- π Microsoft - Asynchronous message-based communication
- π NServiceBus - Outbox
- π Alvaro Herrera - Implement SKIP LOCKED for row-level locks
12.16 Distributed processes
- π HΓ©ctor GarcΓa-Molina, Kenneth Salem - Sagas
- π Caitie McCaffrey - Applying the Saga Pattern
- π Chris Condron - Process Managers Made Simple
- π Martin Schimak - Know the Flow! Events, Commands & Long-Running Services
- π Martin Schimak - Aggregates and Sagas are Processes
- π Martin Schimak - Tackling Complex Event Flows
- π Jean-Philippe DutrΓ¨ve - Messaging Patterns : Flow, SAGA, Messaging Gateway and Observability With RabbitMQ Exchange to Exchange Bindings
- π Udi Dahan - If (domain logic) then CQRS or Saga?
- π Gregor Hohpe - Starbucks Does Not Use Two-Phase Commit
- π Derek ComartinDo you need a Distributed Transaction? Working through a Design Problem
- π Thanh Le - What is SAGA Pattern and How important is it?
- π Jimmy Bogard - Life Beyond Distributed Transactions: An Apostate's Implementation - Relational Resources
- π Microsoft - A Saga on Sagas
- π Microsoft - Design Patterns - Saga distributed transactions pattern
- π Microsoft - Design Patterns - Choreography
- π Chris Richardson - Using sagas to maintain data consistency in a microservice architecture
- π NServiceBus - Sagas
- π NServiceBus sagas: Integrations
- π Denis Rosa (Couchbase) - Saga Pattern | Application Transactions Using Microservices
12.17 Domain Driven Design
- π Eric Evans - DDD Reference
- π Eric Evans - DDD and Microservices: At Last, Some Boundaries!
- π Domain-Driven Design: The First 15 Years
- π Jimmy Bogard - Domain-Driven Design: The Good Parts
- π» Jakub Pilimon - DDD by Examples
- π DDD Quickly
- π Vaughn Vernon - Reactive DDD: Modeling Uncertainty
12.18 Whitepapers
- π Pat Helland - Immutability Changes Everything
- π S. Copei, A. ZΓΌndorf - Commutative Event Sourcing vs. Triple Graph Grammars
- π C. Mohan, D. Haderle, B. Lindsay, H. Pirahesh and P. Schwarz - ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Partial Rollbacks Using Write-Ahead Logging
- π P. O'Neil, E. Cheng, D. Gawlick, E. O'Neil - The Log-Structured Merge-Tree (LSM-Tree)
12.19 Event Sourcing Concerns
- π Kacper Gunia - EventStoreDB vs Kafka
- π Anton StΓΆckl - Event Sourcing: Why Kafka is not suitable as an Event Store
- π Domenic Cassini - Why is Kafka not Ideal for Event Sourcing?
- π Vijay Nair - Axon and Kafka - How does Axon compare to Apache Kafka?
- π Jesper HammarbΓ€ck - Apache Kafka is not for Event Sourcing
- π Udi Dahan - Event Sourcing @ DDDEU 2020 Keynote
- π Andrzej Ludwikowski - Event Sourcing - what could possibly go wrong?
- π Vikas Hazrati - Event Sourcing β Does it make sense for your business?
- π Mikhail Shilkov - Event Sourcing and IO Complexity
- π Dennis Doomen - The Ugly of Event Sourcing - Real-world Production Issues
- π Hugo Rocha - What they donβt tell you about event sourcing
- π Oskar uit de Bos - Stop overselling Event Sourcing as the silver bullet to microservice architectures
<a href='#this-is-not-event-sourcing' id='this-is-not-event-sourcing' class='anchor' aria-hidden='true'></a>
12.20 This is NOT Event Sourcing (but Event Streaming)
- π Confluent - Event sourcing, CQRS, stream processing and Apache Kafka: What's the connection?
- π InfoQ - Building Microservices with Event Sourcing and CQRS
- π Chris Kiehl - Don't Let the Internet Dupe You, Event Sourcing is Hard
- π AWS - Event sourcing pattern
- π Event sourcing with Kafka Streams
- π Event Sourcing with Kafka and ksqlDB
- π Hands On: Trying Out Event Sourcing with Confluent Cloud
- π Andela - Building Scalable Applications Using Event Sourcing and CQRS
- π WiX Engineering - The Reactive Monolith - How to Move from CRUD to Event Sourcing
- π Nexocode - CQRS and Event Sourcing as an antidote for problems with retrieving application states
- π coMakeIT - Event sourcing and CQRS in Action
- π Debezium - Distributed Data for Microservices β Event Sourcing vs. Change Data Capture
- π Codurance - CQRS and Event Sourcing for dummies
- π Slalom Build - Event Sourcing with AWS Lambda
- π AWS Prescriptive Guidance - Decompose monoliths into microservices by using CQRS and event sourcing
- π Zartis - Event Sourcing with CQRS
- π Nordstrom - Event-sourcing at Nordstrom: Part 1
- π Nordstrom - Event-sourcing at Nordstrom: Part 2
- π What is Event Sourcing Design Pattern in Microservice Architecture? How does it work?
- π Techtter - CQRS - Event Sourcing || Deep Dive on Building Event Driven Systems
- π Tech Mind Factory - Event Sourcing with Azure SQL and Entity Framework Core
- π Tech Primers - Event Sourcing & CQRS | Stock Exchange Microservices Architecture | System Design Primer
- π International JavaScript Conference - DDD, event sourcing and CQRS β theory and practice
- π Event Sourcing in NodeJS / Typescript - ESaucy
- π Kansas City Spring User Group - Event Sourcing from Scratch with Apache Kafka and Spring
- π jeeconf - Building event sourced systems with Kafka Streams
- π Jfokus - Event sourcing in practise - lessons learned
- π MecaHumArduino - Event Sourcing on AWS - Serverless Patterns YOU HAVE To Know About
- π Oracle Developers - Event Sourcing, Distributed Systems, and CQRS with Java EE
- π Creating a Blueprint for Microservices and Event Sourcing on AWS
- π Azure Cosmos DB Conf - Implementing an Event Sourcing strategy on Azure
- π CosmosDB DevBlog - Create a Java Azure Cosmos DB Function Trigger using Visual Studio Code in 2 minutes!
- π Towards Data Science - The Design of an Event Store
- π Aspnetrun - CQRS and Event Sourcing in Event Driven Architecture of Ordering Microservices
- π Why Microservices Should use Event Sourcing
- π Event-driven architecture with microservices using event sourcing and CQRS
- π Datomic: Event Sourcing without the hassle
12.21 Architecture Weekly
If you're interested in Architecture resources, check my other repository: https://github.com/oskardudycz/ArchitectureWeekly/.
It contains a weekly updated list of materials I found valuable and educational.
License
This blog is licensed under License Creative Commons BY-SA 4.0.
EventSourcing.NetCore is Copyright Β© 2017-2022 Oskar Dudycz and other contributors under the MIT license.