Event Sourcing and CQRS, Dispatch options.
As seen in previous post, I used dynamic to replay events.
The main reason to use it was to avoid long code using reflection in the infrastructure that would have made it hard to read.
I’ll show several ways to do this dispatch with pros and cons in each cases.
Dynamic
The proposed solution was using dynamic.
+ Pros : there is no reflection code involved, code is very simple.
- Cons : all state change (Apply) methods must have the same name.
I made no performance test, so I cannot judge if perf is better or not. It seems that the DLR has a rather good cache when the same type is encountered several time, but only measures can tell.
Handlers registration
This is the current implementation in Mark Nijhof’s sample.
The base class maintains a dictionary of Type/Action<T> association to dispatch events based on type.
Since an Action<T> delegate must have a target instance, the delegate must be constructed from within the instance, in the .ctor.
public class AggregateRoot<TId>
{
readonly Dictionary<Type, Action<object>> handlers =
new Dictionary<Type, Action<object>>();
protected void Register<T>(Action<T> handler)
{
handlers.Add(typeof(T),e => handler((T)e));
}
protected void Replay(IEnumerable<object> events)
{
foreach (var @event in events)
handlers[@event.GetType()](@event);
}
// rest of the aggregate root class
}
Here is code that use it :
public class Book : AggregateRoot<BookId>
{
private readonly BookId id;
public Book(BookId id,IEnumerable<object> events) : this(id)
{
Replay(events);
}
public Book(BookId id,string title, string isbn) : this(id)
{
var @event = new BookRegistered(id, title, isbn);
OnBookRegistered(@event);
Append(@event);
}
private Book(BookId id)
{
this.id = id;
Register<BookRegistered>(OnBookRegistered);
Register<BookLent>(OnBookLent);
Register<BookReturned>(OnBookReturned);
}
private void OnBookRegistered(BookRegistered @event) { /**/ }
private void OnBookLent(BookLent @event) { /**/ }
private void OnBookReturned(BookReturned @event) { /**/ }
}
+Pros : Still no reflection,
Meaningful method names
-Cons : Additional plumbing code,
Private constructor to avoid repetition
Registration occurs at each instantiation
Convention Based Method Naming
This is the way advocated by Greg Young.
If your event is called BookRegistered, assume the method will be called OnBookRegistered, and find it by reflection. You can implement a cache at class level to avoid reflection on each dispatch.
public abstract class AggregateRoot<TId> : IAggregateRoot<TId>
{
private static readonly Dictionary<Type, IEventDispatcher> Handlers =
new Dictionary<Type, IEventDispatcher>();
private static readonly object HandlersLock = new object();
protected void Replay(IEnumerable<object> events)
{
var dispatcher = GetDispatcher();
dispatcher.Dispatch(this, @events);
}
private IEventDispatcher GetDispatcher()
{
IEventDispatcher handlers;
var type = GetType();
lock (HandlersLock)
{
if (!Handlers.TryGetValue(type, out handlers))
{
handlers = EventDispatcher.Create(type);
Handlers.Add(type, handlers);
}
}
return handlers;
}
... rest of the code here
}
The dispatcher code :
internal interface IEventDispatcher
{
void Dispatch(object target, IEnumerable<object>events);
}
internal class EventDispatcher<T> : IEventDispatcher
{
private readonly Dictionary<Type, IEventHandler<T>> handlers;
public EventDispatcher()
{
var h = from m in typeof(T)
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
let parameters = m.GetParameters()
where parameters.Length ==1
&& m.Name == "On" + parameters[0].ParameterType.Name
select EventHandler.Create<T>(m);
handlers = h.ToDictionary(i => i.EventType);
}
public void Dispatch(object target, IEnumerable<object> events)
{
var typedTarget = (T)target;
foreach (var @event in events)
{
var handler = handlers[@event.GetType()];
handler.Call(typedTarget, @event);
}
}
}
internal static class EventDispatcher
{
public static IEventDispatcher Create(Type type)
{
return (IEventDispatcher)Activator.CreateInstance(
typeof(EventDispatcher<>).MakeGenericType(type));
}
}
and the event handler :
internal interface IEventHandler<T>
{
void Call(T target, object argument);
Type EventType { get; }
}
internal class EventHandler<TEntity, TEvent> : IEventHandler<TEntity>
{
private readonly Action<TEntity, TEvent> handler;
public EventHandler(MethodInfo methodInfo)
{
handler = (Action<TEntity, TEvent>)Delegate.CreateDelegate(
typeof(Action<TEntity, TEvent>), methodInfo, true);
}
public void Call(TEntity target, object argument)
{
handler(target, (TEvent)argument);
}
public Type EventType
{
get { return typeof(TEvent); }
}
}
internal static class EventHandler
{
public static IEventHandler<T> Create<T>(MethodInfo methodInfo)
{
var eventType = methodInfo.GetParameters()[0].ParameterType;
return (IEventHandler<T>)Activator.CreateInstance(
typeof(EventHandler<,>)
.MakeGenericType(typeof(T), eventType),
methodInfo
);
}
}
The trick here is to create a static delegate with two parameters from an instance method info that take one parameter (and one implicit this target).
This way, the delegate is not tied to a specific instance and can be used on any target.
As you can see, this option requires more code ! I did not want to start with that.
+Pros : Convention base names mean no manual mapping, mapping is implicit
Binding is made a class level instead of instance level
-Cons : Only unit tests can tell when you mess with names
Not immune to event name change, should have good unit tests !
Apply then Append
I also had a remark that if I forget Append after Apply, I’ll get in trouble.
In Handler Registration option and Convention base method naming, the dispatch can be done by the base class, so I could tell the base class to dispatch then Append then event to UncommittedEvents.
This way you end with something like :
var @event = new BookLent(/**/);
Play(@event);
where play dispatches the event to the right method and appends.
This way you cannot forget.
My problem with this, especially in the Convention base method naming scenario is that nobody references the event application methods anymore. Resharper will report them as unused methods, and you won’t know unless you run unit tests.
Moreover, you pay the cost of a dynamic dispatch when you know your event type.
Perhaps something like this could be better :
var @event = new BookLent(/**/);
Play(@event).With(OnBookLent);
the implementation is not very complicated :
public class AggregateRoot<TId>
{
private readonly UncommittedEvents uncommittedEents;
protected EventPlayer<TEvent> Play<TEvent>(TEvent @event)
{
return new EventPlayer<TEvent>(@event, uncommitedEvents);
}
... rest of the code here
}
public struct EventPlayer<TEvent>
{
private readonly TEvent @event;
private readonly UncommittedEvents uncommittedEvents;
internal EventPlayer(TEvent @event, UncommittedEvents uncommittedEvents)
{
this.@event = @event;
this.uncommittedEvents = uncommittedEvents;
}
public void With(Action<TEvent> handler)
{
handler(@event);
uncommittedEvents.Append(@event);
}
}
This way, methods are referenced at least once with type check.
My mind is still not set… What do you prefer ?