I’m not going to go into detail about what the deal is about event handlers in a CQRS architecture, since a quick Google/Bing search will give plenty of very good information. What this post is about is a solution to the “how do I quickly register something to handle a bunch of events” without copying pasting all over the place. There are other solutions out there, like this one. Here’s something I came up with (took some concepts from my post on Weak Events).
public class Aggregate{private delegate void OpenEventHandler<in TTarget, in TEvt>(TTarget target, TEvt @event);private static readonly IDictionary<Type, OpenEventHandler<Game, IEvent>> _evtHandlers = new Dictionary<Type, OpenEventHandler<Game, IEvent>>();static Aggregate()
{var methods = from m in typeof(Game).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)let p = m.GetParameters()where m.Name == "ApplyEvent" && p.Length == 1 && typeof(IEvent).IsAssignableFrom(p[0].ParameterType)select m;var registerForwarder = typeof(Game).GetMethod("RegisterForwarder", BindingFlags.NonPublic | BindingFlags.Static);foreach (var m in methods){Type eventType = m.GetParameters()[0].ParameterType;var forwarder = registerForwarder.MakeGenericMethod(eventType).Invoke(null, new[] { m });_evtHandlers[eventType] = (OpenEventHandler<Game, IEvent>)forwarder;}}private static OpenEventHandler<Game, IEvent> RegisterForwarder<TEvt>(MethodInfo method){var invoker = typeof(OpenEventHandler<,>).MakeGenericType(typeof(Game), typeof(TEvt));var forwarder = (OpenEventHandler<Game, TEvt>)Delegate.CreateDelegate(invoker, null, method);
return (g, e) => forwarder(g, (TEvt)e);
}private void ApplyEvent(EventHappened e){_something = e.Something;}public void ApplyChanges(IEnumerable<IEvent> events){foreach (var e in events){_evtHandlers[e.GetType()](this, e);
}}}
A couple things:
- The registration happens in the static constructor. This is important, because this relatively heavy cost of using reflection only happens once for the aggregate.
- The filtering of methods is arbitrary. I chose “ApplyEvent” here as the convention, but of course you can choose whatever you like.
- ApplyChanges simply invokes the event handlers dictionary directly. Assuming you’re being a good citizen with the code, accessing _evtHandlers doesn’t need a lock because once created it should never be modified.
So in summary, it finds all methods named ApplyEvent in the current class, and generates an “open delegate” which takes in an extra parameter which is the instance itself. In this case, the instance is the aggregate, as shown in the ApplyChanges method.
So there you have it! Excluding the lengthy LINQ query, roughly 10 lines of code to find and register all event handlers in the aggregate. And if you’re wondering, the performance cost is negligible because there’s no reflection involved in the invocation of the handlers. Awesome!