Continuing this series on CQRS and ES in .NET, we’re now going to focus on Event Handlers. I previously covered:
If you haven’t read them, I advise you to to so first. Read them in order, because they build upon each other. If you’re already familiar with CQRS and Event Sourcing, you can skip the first one.
As you might recall, when we modify an aggregate, it stores pending events in a private list. When the repository is tasked to save the aggregate, it only persists these pending events. But we need to do more with these events. We need to pass them to event handlers.
These event handlers can update a read model, but might also perform other tasks. An example I’ve encountered was sending out a mail when certain events had happened.
The interface of an event handler is simple:
/// <summary>
/// Marker interface for dependency injection
/// </summary>
public interface IEventHandler {}
public interface IEventHandler<in T> : IEventHandler where T : IEvent
{
void Handle(T e);
}
Let’s continue our example, and assume that we have a read model of all the customer’s names. In our overly simplified example, this could be a table with a column containing customer names, and a column with customer Id’s. If we need to show a list of names in a UI, we would only need to query this table.
A real-life application would have slightly more complex read models, but the principle will stay the same. Try to query as few tables as possible for a given screen, ideally one. Keep your table up to date, with all necessary data, using the events that your system is generating.
An event handler to update this table of customer names could look like this:
public class CustomerNameReadModelGenerator :
IEventHandler<CustomerCreated>,
IEventHandler<NameUpdated>
{
private readonly DbContext _dbContext;
public CustomerNameReadModelGenerator(DbContext dbContext)
{
_dbContext = dbContext;
}
public void Handle(CustomerCreated e)
{
var customerName = new CustomerName
{
CustomerId = e.SourceId,
Name = e.Name
};
_dbContext.CustomerNames.Add(customerName);
_dbContext.SaveChanges();
}
public void Handle(NameUpdated e)
{
var customerName = _dbContext.CustomerNames.Single(x => x.CustomerId == e.SourceId);
customerName.Name = e.NewName;
_dbContext.SaveChanges();
}
}
Your implementation may vary. I’d especially advise you to put the DbContext behind an abstraction, for unit testing purposes. But for our example, this is sufficient. The idea is to update the CustomerName table in the database to reflect the changes stored in the events.
But how do we get from the Repository to these event handlers?
Bridging The Gap
CQRS/ES systems are ideal for applications that need a higher level of performance, are more complex, and can live with eventual consistency. Eventual consistency is where the change requested by a user might take some time to be visible in a UI. “Some time” could mean minutes, seconds, milliseconds or even less. But the idea is that your system won’t guarantee the change to be consistent in all parts of the system after it has returned control back to the user.
I won’t go into details here, because it would take us too far. Search the internet for “eventual consistency” and you will definitely find great articles explaining it.
What typically happens in CQRS/ES systems is that the persisted events are put on some sort of message bus system for the event handlers to receive. In the save method of theĀ Repository, there was a call to an eventbus:
eventSourced.Events.ToList().ForEach(e =>
{
_eventBus.Publish(e, correlationId);
});
The _eventBus parameter was an abstraction that you can implement in whatever way you want. The important thing here is to be able to receive your event as it was sent. Serializing to and deserializing from JSON are typical techniques here.
It would even be possible to do this all in memory, without a real messaging system (like RabbitMQ). If you’re interested in this, let me know, I’ve implemented this before.
At the receiving side, you will need a collection of all your event handlers and call the correct one. This is a way I’ve done it:
public class MessageReceiver
{
private readonly IDictionary<Type, IList<Action<IEvent>>> _eventHandlerActions = new Dictionary<Type, IList<Action<IEvent>>>();
/// <summary>
/// You should have a dependency injection system using the IEventHandler marker interface to inject all your event handlers.
/// This method will build a dictionary that can be used to match
/// an event type to a list of event handlers.
/// </summary>
public MessageReceiver(IEnumerable<IEventHandler> eventHandlers)
{
foreach (var eventHandler in eventHandlers)
{
var eventHandlerType = eventHandler.GetType();
if (typeof(IEventHandler<>).IsAssignableFrom(eventHandlerType)) {
return;
}
var handleMethods = eventHandlerType
.GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Where(m => m.Name == "Handle" && m.GetParameters().Length == 1);
foreach (var handleMethod in handleMethods) {
var eventType = handleMethod.GetParameters().Single().ParameterType;
if (!_eventHandlerActions.ContainsKey(eventType)) {
_eventHandlerActions[eventType] = new List<Action<IEvent>>();
}
_eventHandlerActions[eventType].Add(e => { handleMethod.Invoke(eventHandler, new[] {e}); });
}
}
}
/// <summary>
/// This method should receive the (deserialized) event
/// </summary>
public void Receive(IEvent e)
{
IList<Action<IEvent>> actions;
_eventHandlerActions.TryGetValue(e.GetType(), out actions);
if (actions == null || !actions.Any()) {
return;
}
foreach (var action in actions) {
action(e);
}
}
}
Like many parts of our CQRS/ES system, this might seem quite complex. But once you have it set up, you hardly ever need to look at it anymore. You can then focus on the real business-logic of your event handlers.
That’s All Folks!
Now unless I’m mistaken, I think I’ve covered all parts to implement CQRS/ES in .NET. We’ve gone covered
- receiving a Command DTO
- handling it in the Command Handler
- using the Repository to rehydrate, update and save our Domain object
- updating our read model in Event Handlers (or acting on events in other ways)
This was a lengthy multi-part series, but don’t be frightened by it all. CQRS/ES in .NET is actually quite easy. Once you have some experience with it all, you’ll encounter issues that have more complex solutions (like saga’s). But that’s true for any system, language, or design pattern. That’s true for software development in general, and that didn’t stop you, did it?
Having looked into CQRS/ES more lately, I’ve encountered a framework that does much of this for you: EventFlow. I have no experience with it, but it might be worth checking out. Though setting up your own isn’t that much work, as I hope my series have made clear.
But once you have your first experience with CQRS/ES, you’ll notice how fine it is to work with. It fits greatly in Domain Driven Design, allows for very readable code, especially because the commands map directly to what the user wants to achieve. My feeling is you’ll use more natural language instead of technical language in your code. Don’t think that CQRS/ES is now the solution to all your problems, but it’s definitely another tool in your toolbelt.