// thinkbeforecoding

Event Sourcing and CQRS, Dispatch options 2

2011-06-21T08:38:57 / jeremie chassaing

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.