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 ?