

Event Sourcing Example & Explained in plain English
source link: https://codeopinion.com/event-sourcing-example-explained-in-plain-english/?utm_campaign=event-sourcing-example-explained-in-plain-english
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What is Event Sourcing? It’s a way of storing data that is probably very different than what you’re used to. I’ll explain the differences and show ab event sourcing example that should clear up all the mystery around it.
YouTube
Check out my YouTube channel where I post all kinds of content that accompanies my posts including this video showing everything that is in this post.
Source Code
Developer-level members of my CodeOpinion YouTube channel get access to the full source in this post available in a git repo. Check out the membership for more info.
Current State
The vast majority of apps persist current state in a database. Regardless if you’re using a relational database (RDBMS) or a document store (NoSQL), you’re likely used to storing current state.
To illustrate this, here’s a table that represents products.
If we were to have some behavior in our system that is to receive product into the warehouse, we would increment the quantity value. So for example, if we received more quantity for SKU ABC123, we would update quantity value. If we shipped product out of the warehouse, we would decrease the quantity value.
One question to ask from the table above, how did we get to the current state of Quantity of 59 for product ABC123?
Because we only record current state, we have no way to know with absolutely certainty how we got to that number.
Yes, if added logging you could infer with some degree of certainty how you got to a particular state. However, it would not be guaranteed because it actually requires you to write logs in every place that you’re changing state, include outside of your application. This could be incredibly difficult. Ultimately your current state is the point of truth, no matter how you got to that state.
Event Sourcing
Event Sourcing is a different approach to storing data. Instead of storing the current state, you’re instead going to be storing events. Events represent the state transitions of things that have occurred in your system.
They are facts.
To illustrate the exact same product of SKU ABC123 that had a current state quantity of 59, this is how we would be storing this data using event sourcing.
It’s important to note that events are persisted in what is called an event stream. Event streams are generally per unique aggregate. So in my example, a single product SKU. The above is the stream of events for SKU ABC123.
With the above events, we can see that we Received a quantity of 10. Then we Received 5 more. Followed up by having Shipped out 6. Finally there some extra quantity that was magically found in the warehouse so it was adjusted by another 50. This got us to our current state of 59.
Event Sourcing Implementation
First is to define the events that occur that we want to record. Events are facts that something has occurred. They are generally the result of a state changes from commands. Here’ are the 3 events I’ve defined in our the event stream.
public interface IEvent {}
public record ProductShipped(string Sku, int Quantity, DateTime DateTime) : IEvent;
public record ProductReceived(string Sku, int Quantity, DateTime DateTime) : IEvent;
public record InventoryAdjusted(string Sku, int Quantity, string Reason, DateTime DateTime) : IEvent;
Next comes our aggregate. It is responsible for creating events that will get persisted to the event stream. The aggregate exposes methods to perform commands/actions to our domain. If our business logic passes, then we’ve confirmed that an event has occured.
public class CurrentState { public int QuantityOnHand { get; set; } }
public class WarehouseProduct { public string Sku { get; } private readonly IList<IEvent> _allEvents = new List<IEvent>(); private readonly IList<IEvent> _uncommittedEvents = new List<IEvent>();
// Projection (Current State) private readonly CurrentState _currentState = new();
public WarehouseProduct(string sku) { Sku = sku; }
public void ShipProduct(int quantity) { if (quantity > _currentState.QuantityOnHand) { throw new InvalidDomainException("Ah... we don't have enough product to ship?"); }
AddEvent(new ProductShipped(Sku, quantity, DateTime.UtcNow)); }
public void ReceiveProduct(int quantity) { AddEvent(new ProductReceived(Sku, quantity, DateTime.UtcNow)); }
public void AdjustInventory(int quantity, string reason) { if (_currentState.QuantityOnHand + quantity < 0) { throw new InvalidDomainException("Cannot adjust to a negative quantity on hand."); }
AddEvent(new InventoryAdjusted(Sku, quantity, reason, DateTime.UtcNow)); }
private void Apply(ProductShipped evnt) { _currentState.QuantityOnHand -= evnt.Quantity; }
private void Apply(ProductReceived evnt) { _currentState.QuantityOnHand += evnt.Quantity; }
private void Apply(InventoryAdjusted evnt) { _currentState.QuantityOnHand += evnt.Quantity; }
public void ApplyEvent(IEvent evnt) { switch (evnt) { case ProductShipped shipProduct: Apply(shipProduct); break; case ProductReceived receiveProduct: Apply(receiveProduct); break; case InventoryAdjusted inventoryAdjusted: Apply(inventoryAdjusted); break; default: throw new InvalidOperationException("Unsupported Event."); }
_allEvents.Add(evnt); }
public void AddEvent(IEvent evnt) { ApplyEvent(evnt); _uncommittedEvents.Add(evnt); }
public IList<IEvent> GetUncommittedEvents() { return new List<IEvent>(_uncommittedEvents); }
public IList<IEvent> GetAllEvents() { return new List<IEvent>(_allEvents); }
public void EventsCommitted() { _uncommittedEvents.Clear(); }
public int GetQuantityOnHand() { return _currentState.QuantityOnHand; } }
public class InvalidDomainException : Exception { public InvalidDomainException(string message) : base(message) {
} }
When an event is added, we call the appropriate Apply() method. These methods are to keep track of the current state within our aggregate so we can perform the relevant business logic (which throws an InvalidDomainException).
Repository
For demo purposes, I’m not using an actual database to store our event stream, but rather just in-memory dictionary and list to illustrate. The important part is your repository is responsible two things, building your aggregate and saving the events from your aggregate.
public class WarehouseProductRepository { private readonly Dictionary<string, List<IEvent>> _inMemoryStreams = new();
public WarehouseProduct Get(string sku) { var warehouseProduct = new WarehouseProduct(sku);
if (_inMemoryStreams.ContainsKey(sku)) { foreach (var evnt in _inMemoryStreams[sku]) { warehouseProduct.ApplyEvent(evnt); } }
return warehouseProduct; }
public void Save(WarehouseProduct warehouseProduct) { if (_inMemoryStreams.ContainsKey(warehouseProduct.Sku) == false) { _inMemoryStreams.Add(warehouseProduct.Sku, new List<IEvent>()); }
var newEvents = warehouseProduct.GetUncommittedEvents(); _inMemoryStreams[warehouseProduct.Sku].AddRange(newEvents); warehouseProduct.EventsCommitted(); } }
When you want to build/get a WarehouseProduct from the Repository, it will get the events from the event stream, then call ApplyEvent() for each existing event. This is replaying all the events in the aggregate to get back to current state.
var warehouseProduct = warehouseProductRepository.Get(sku); warehouseProduct.ReceiveProduct(quantity); warehouseProductRepository.Save(warehouseProduct);
Then after you have called commands like ShipProduct/ReceiveProduct/AdjustInventory, the new events will get appended to the event stream from the Repositories Save() method.
Projections
The current state used in the aggregate is called a projection. It represents the current state of the event stream. I’ve covered more about projections and how they are used in UI and reporting to build many different models from your event stream.
Demo App
I’ve created a simple console application that has all the code above in an interactive way. Developer-level members of my CodeOpinion YouTube channel get access to the full source and demo available in a git repo. Check out the membership for more info.
Follow @CodeOpinion on TwitterLeave this field empty if you're human:
Recommend
-
89
And in-depth article that explores JWT structure in-depth, shows it's most common usages related to client-side sessions and, provides an overview of cryptographic features like signing and encryption
-
90
A plain English introduction to JSON web tokens (JWT): what it is and what it isn’t ...
-
11
RSpec mocks and stubs in plain English by Jason Swett, January 29, 2019 One of the most common questions I see from beginners...
-
24
What is WSGI and CGI in plain English? advertisements Every time I read either WSGI or CGI I cringe. I've tried reading on it before but nothi...
-
11
Bitcoin Basics: The phenomenon explained in plain english Bitcoin is a new financial paradigm that could change the future of finance forever. In Bitcoin Basics: The phenomenon explained in pla...
-
5
How to explain Business Intelligence (BI) in plain EnglishThis website uses cookiesWe use cookies to offer you a better browsing experience, analyze site traffic, personalize content and serve targeted ads. Learn about how we use cook...
-
15
October 5, 2021 / #JavaScript JavaScript Callback Function –Explained in Pl...
-
9
Want to see an example of how CQRS & Event Sourcing work together? Here’s a code walk-through that illustrates sending commands to your domain that stores Events in an Event Store. The Events are then published to Consumers that updated P...
-
9
Covariance, Invariance and Contravariance explained in plain English? advertisements Today, I read some articles about Cova...
-
10
You might have heard of the term User Experience or UX design. But what does it mean exactly?In this article, I will explain what UX design is and talk about how to become a UX designer. What is UX design?
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK