Event Sourcing and CQRS, Dispatch options 2
In the part one comments, Clement suggested a more efficient solution than registering handler in constructor.
The proposed solution is to have a RegisterAllEvents virtual method in which event handler registration would occur. This method is a method instance to have access to this but will be called only once per class. The registration use Expression<Action<T>> to access the expression tree and extract the method info of the handler. This enables type checking, make R# happy – no unused methods – and make reflection not too painful.
Good solution.
I didn't go that far because with Event Sourcing, you usually keep aggregates in memory, so aggregates are instantiated once per service lifetime.
I just crafted a small performance test :
using System;
using System.Collections.Generic;
using System.Diagnostics;
namespace AggregatePerfTest
{
class Program
{
static void Main(string[] args)
{
var watch = new Stopwatch();
const int count = 10000000;
Guid id = Guid.NewGuid();
watch.Start();
for (int i = 0; i < count; i++)
new AggregateRegisteredOncePerInstance(id);
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
watch.Reset();
watch.Start();
for (int i = 0; i < count; i++)
new AggregateRegisteredOncePerClass(id);
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
}
}
public class AggregateRegisteredOncePerClass
{
private readonly Guid id;
private static readonly object ClassInitLock = new object();
private static bool initialized;
public AggregateRegisteredOncePerClass(Guid id)
{
this.id = id;
lock (ClassInitLock)
{
if (!initialized)
{
initialized = true;
// registration happens only once here
}
}
}
public Guid Id
{
get { return id; }
}
}
public class AggregateRegisteredOncePerInstance
{
private readonly Guid id;
private readonly Dictionary<Type, dynamic> handlers =
new Dictionary<Type, dynamic>(5);
public AggregateRegisteredOncePerInstance(Guid id)
{
this.id = id;
Register<int>(OnSomethingHappened);
Register<double>(OnSomethingHappened);
Register<float>(OnSomethingHappened);
Register<long>(OnSomethingHappened);
}
public Guid Id
{
get { return id; }
}
public void DoSomething()
{
Apply(1);
}
private void OnSomethingHappened(int message) { }
private void OnSomethingHappened(double message){ }
private void OnSomethingHappened(float message) { }
private void OnSomethingHappened(long message) { }
protected void Apply<T>(T @event)
{
handlers[typeof (T)](@event);
}
protected void Register<T>(Action<T> handler)
{
handlers.Add(typeof(T), handler);
}
}
}
The code is straight forward, I just created two aggregate classes :
- one with registration in .ctor based on this post code
- one without any registration at all, considering that doing it once is the same as not doing it for large numbers, but I added a lock section with a boolean check to simulate what will done on each instance creation.
I created 10.000.000 instances for each, and you get:
- 3978ms for the one with .ctor registrations,
- 377 ms for the one without.
It's true that it makes a difference. But how many aggregates do you have in your system ?
With 10.000 aggregate you're still under 8ms. I think you can afford that.
This is then a trade-off between performance and simplicity :
- If you have very large numbers, go for expression tree parsing, class lock management etc.
- In any other situation I recommend using registration in .ctor that makes the code easy to implement in approximately 5min.