I’m a big advocate of the Domain Events Pattern. It allows us to respond to events occurring in our domain in a loosely coupled way.
The difference between Domain events and traditional message based eventing is typically they are handled synchronously.
To dispatch events from our domain we use the following class:
public static class DomainEvents
{
public static IEventDispatcher Dispatcher { get; set; }
static DomainEvents()
{
Dispatcher = new EmptyDispatcher();
}
public static void Raise<TEvent>(TEvent e)
{
if (e != null)
{
Dispatcher.Dispatch(e);
}
}
private class EmptyDispatcher : IEventDispatcher
{
public void Dispatch<TEvent>(TEvent e) { }
}
}
The DomainEvents
class just provides a static entry point into a IEventDispatcher
. We’d raise events from our domain objects like so:
public void Ship(string trackingCode)
{
TrackingCode = trackingCode;
DomainEvents.Raise(new OrderShippedEvent(this));
}
The DefaultDispatcher
dispatches events as soon as they are raised:
public class DefaultDispatcher : IEventDispatcher
{
private readonly Dictionary<Type, Collection<Delegate>> handlers;
public DefaultDispatcher()
{
handlers = new Dictionary<Type, Collection<Delegate>>();
}
public void Register<TEvent>(Action<TEvent> handler)
{
Collection<Delegate> eventHandlers;
if (!handlers.TryGetValue(typeof(TEvent), out eventHandlers))
{
eventHandlers = new Collection<Delegate>();
handlers.Add(typeof(TEvent), eventHandlers);
}
eventHandlers.Add(handler);
}
public void Dispatch<TEvent>(TEvent e)
{
Collection<Delegate> eventHandlers;
if (handlers.TryGetValue(typeof(TEvent), out eventHandlers))
{
foreach (var handler in eventHandlers.Cast<Action<TEvent>>())
{
try
{
handler(e);
}
catch
{
// log
}
}
}
}
}
Here’s an end-to-end example of dispatching an OrderShippedEvent
:
// handler registration typically done at app startup
var dispatcher = new DefaultDispatcher();
dispatcher.Register<OrderShippedEvent>(
e => Console.WriteLine("Order {0} shipped with tracking code {1}", e.OrderId, e.TrackingCode));
DomainEvents.Dispatcher = dispatcher;
var order = new Order("orders/1", 1.99M);
order.Ship("1230XYZ");
Recently I had the need to defer the dispatch of events.
We were using the domain events pattern in a web application and using session-per-request to commit changes at the end of the http request.
Since we were using MSSQL to generate entity identifiers it meant that we did not have access to the id until we had committed our unit of work at the end of the request.
Some of our domain handlers were firing off background processes that needed to load the entity from the database. This meant we had two problems:
The entity did not exist in the database at the point the event was dispatched.
The entity id we were sending in the event would be 0 (
default(int)
)).
At first we resorted to a hack of explicitly committing the unit of work in our controllers:
public ActionResult PlaceAndShipOrder(OrderDetails details)
{
var order = new Order(details.ProductId);
// HACK: we need the order to exist in db before we Ship
session.Save(order);
uow.Commit();
order.Ship(details.TrackingCode);
return View("shipped");
}
As the sleepless nights were getting to me I had to come up with a better solution, which ended up being pretty simple.
I created a deferred event dispatcher that as the name suggests, defers the dispatch of events until we call Resolve()
- Inspired by promises in jQuery.
public class DeferredEventDispatcher : IEventDispatcher
{
private readonly IEventDispatcher inner;
private readonly ConcurrentQueue<Action> events = new ConcurrentQueue<Action>();
public DeferredEventDispatcher(IEventDispatcher inner)
{
this.inner = inner;
}
public void Dispatch<TEvent>(TEvent e)
{
events.Enqueue(() => inner.Dispatch(e));
}
public void Resolve()
{
Action dispatch;
while (events.TryDequeue(out dispatch))
{
dispatch();
}
}
}
This class simply wraps an existing dispatcher and queues the handlers until we call Resolve
.
Now at the end of the request we commit our unit of work and then dispatch the events. Example:
var dispatcher = new DefaultDispatcher();
dispatcher.Register<OrderShippedEvent>(
e => Console.WriteLine("Order {0} shipped with tracking code {1}", e.OrderId, e.TrackingCode));
var deferred = new DeferredEventDispatcher(dispatcher);
DomainEvents.Dispatcher = deferred;
var order = new Order("orders/1", 1.99M);
order.Ship("1230XYZ");
uow.Commit();
// now raise events
deferred.Resolve();
Of course a better solution would be to use Guid identifiers generated on the client (common practice in message based architectures) but this is a reasonable solution for now.