I've been using the Domain Events pattern for some time - it enables us to encapsulate as much behaviour in our domain layer as possible and provides a nice way for other parts of our application to subscribe to domain events.
Currently we're using a static class that our domain objects can call to raise events:
static class DomainEvents
{
public static IEventDispatcher Dispatcher { get; set; }
public static void Raise<TEvent>(TEvent e)
{
if (e != null)
{
Dispatcher.Dispatch(e);
}
}
}
As you can see, this is little more than a shim to an IEventDispatcher
that actually does the work of dispatching or publishing the events.
Our dispatcher implementation simply uses our IoC container (StructureMap) to locate event handlers for the specified type of event.
public void Dispatch<TEvent>(TEvent e)
{
foreach (var handler in container.GetAllInstances<IHandler<TEvent>>())
{
handler.Handle(e);
}
}
This works okay in most cases. However, there are a few problems with this approach:
Events should only be dispatched if the entity is successfully persisted
Take the following class:
public class Order
{
public string Id { get; private set; }
public decimal Amount { get; private set; }
public Order(decimal amount)
{
Amount = amount;
DomainEvents.Raise(new OrderRaisedEvent { OrderId = Id });
}
}
In the Order
constructor we raise an OrderRaisedEvent
. In our application layer we'd likely create the order instance, add it to our database "session" and then commit/save the changes:
var order = new Order(amount: 10);
session.Store(order);
session.SaveChanges();
The problem here is that the domain event is raised before we have successfully saved our Order entity (committing the transaction). If the save failed we would have still dispatched the events.
A better approach would be to queue the events until the entity is persisted. However, I'm unsure how best to implement this whilst maintaining strongly typed event handlers.
Events should not be created until the entity is persisted
Another issue I'm facing is that our entity identifiers are not set/assigned until store the entity (RavenDB - session.Store
). This means that in the above example, the order identifier passed to the event is actually null
.
Since I'm not sure how can actually generate RavenDB identifiers upfront, one solution could be to delay the creation of the events until the entity is actually saved but again I'm not how best to implement this - perhaps queuing a collection of Func<TEntity, TEvent>
?