I recently implemented Domain Events as a way of organising domain logic in our application. To summarise how they work:
- Every event in your application is modelled as a class which implements the empty
IDomainEvent
interface IDomainEvent
s are raised by a domain object calling a staticDomainEvents.Raise<T>(T domainEvent) where T : IDomainEvent
method- The
DomainEvents
class passes raisedIDomainEvent
objects to theHandle<T>(T domainEvent)
method of classes which implement theIDomainEventHandler<T>
interface for the type ofIDomainEvent
raised
One task more or less left to the reader in the blog above is how the DomainEvent
s class finds all
the IDomainEventHandler
s to which it has to pass IDomainEvent
s. I (as usual) didn’t want to
bother registering handlers, so I made some changes to have them register themselves.
So as an example, here’s an IDomainEvent
which signals that an Order
has been placed:
public class OrderPlaced : IDomainEvent
{
private readonly Order _order;
public OrderPlaced(Order order)
{
_order = order;
}
public Order PlacedOrder
{
get { return _placedOrder; }
}
}
…and an IDomainEventHandler
which sends a confirmation email when the event is raised:
public class NotificationEmailDomainEventHandler :
IDomainEventHandler<OrderPlaced>
{
public void Handle(OrderPlaced domainEvent)
{
// send an email using domainEvent.PlacedOrder;
{
}
The static DomainEvents
class is a singleton
which uses an IDomainEventHandlerLibrary
to access the IDomainEventHandler
s to which it
should pass raised IDomainEvent
s. The singleton instance is created by the InjectionService
;
a static class which wraps the application’s dependency injection container and supplies the
IDomainEventHandlerLibrary
. The IDomainEventHandlerLibrary
interface is as follows:
public interface IDomainEventHandlerLibrary
{
IEnumerable<IDomainEventHandler<T>> GetEventHandlers<T>(
T domainEvent);
}
…and the DomainEvents
class looks like this:
public class DomainEvents
{
private static readonly DomainEvents _instance =
InjectionService.Resolve<DomainEvents>();
private readonly IDomainEventHandlerLibrary _handlerLibrary;
public DomainEvents(IDomainEventHandlerLibrary handlerLibrary)
{
_handlerLibrary = handlerLibrary;
}
public static void Raise<T>(T domainEvent)
where T : IDomainEvent
{
_instance.InstanceRaise(domainEvent);
}
private void InstanceRaise<T>(T domainEvent)
where T : IDomainEvent
{
IEnumerable<IDomainEventHandler<T>> eventHandlers =
_handlerLibrary.GetEventHandlers(domainEvent);
if (eventHandlers != null)
{
foreach (IDomainEventHandler<T> handler in eventHandlers)
{
handler.Handle(domainEvent);
}
}
}
}
A self-configuring implementation of IDomainEventHandlerLibrary
then looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
public class SelfConfiguredDomainEventHandlerLibrary :
IDomainEventHandlerLibrary
{
private static Dictionary<Type, List<IDomainEventHandler>>
_eventHandlerCache = CreateEventHandlerCache();
public IEnumerable<IDomainEventHandler<T>> GetEventHandlers<T>(
T domainEvent)
where T : IDomainEvent
{
if (!_eventHandlerCache.ContainsKey(typeof(T)))
{
return null;
}
return _eventHandlerCache[typeof(T)]
.Cast<IDomainEventHandler<T>>()
.ToArray();
}
private static Dictionary<Type, List<IDomainEventHandler>>
CreateEventHandlerCache()
{
var eventHandlerCache =
new Dictionary<Type, List<IDomainEventHandler>>();
Assembly
.GetExecutingAssembly()
.GetAvailableTypes(typeFilter: t =>
!t.IsInterface &&
typeof(IDomainEventHandler).IsAssignableFrom(t))
.Select(t =>
(IDomainEventHandler)InjectionService.Resolve(t))
.ForEach(eh =>
{
eh.GetType()
.GetInterfaces()
.Where(it =>
it.IsGenericType &&
typeof(IDomainEventHandler)
.IsAssignableFrom(it))
.ForEach(it =>
{
Type handledDomainEventType =
it.GetGenericArguments().First();
if (!eventHandlerCache
.ContainsKey(handledDomainEventType))
{
eventHandlerCache.Add(
handledDomainEventType,
new List<IDomainEventHandler>());
}
eventHandlerCache[handledDomainEventType]
.Add(eh);
});
});
return eventHandlerCache;
}
}
A couple of things to note:
- The generic
IDomainEventHandler<T>
interface is derived from an empty, non-genericIDomainEventHandler
interface in order for this to work - The
Assembly.GetAvailableTypes()
method is an extension method of mine
Comments
3 comments