I am trying to implement a domain event system that only fires its events when the associated unit-of-work commits successfully.
The main reason why I want to do this is because I have other sub-systems that read the database that expect those changes to be in place when handling the event.
I also don't want event handlers to do their work (send out emails, etc.) if the commit fails.
The system is multiple ASP.NET applications (WebForms, MVC, and WebAPI).
public class Order
{
private Payment _payment;
public int ID { get; private set; }
public decimal Amount { get; private set; }
public void PayUsing(IPaymentProcessor processor)
{
if (_payment != null)
{
throw new InvalidOperationException(
"You can't pay for an order twice");
}
// the Process method may also raise domain events
_payment = processor.Process(Amount);
// Raise saves the event into a static Queue<T>
DomainEvents.Raise(new OrderPaidEvent(this));
}
}
public class OrderFulfillmentService
{
private readonly IUnitOfWorkFactory _unitOfWorkFactory;
private readonly IPaymentProcessor _paymentProcessor;
public OrderFulfillmentService(
IUnitOfWorkFactory unitOfWorkFactory,
IPaymentProcessor paymentProcessor)
{
_unitOfWorkFactory = unitOfWorkFactory;
_paymentProcessor = paymentProcessor;
}
public void Fulfill(int orderId)
{
using (var unitOfWork = _unitOfWorkFactory.Create())
{
var order = unitOfWork.OrdersRepository.GetById(orderId);
order.PayUsing(_paymentProcessor);
unitOfWork.Commit();
// I only want the events to be raised if the Commit succeeds
}
}
}
With the current implementation, it's problematic because right now the domain raising must put it into a queue as it must not fire immediately. The queue is static which causes problems for multi-user systems. I do not want to accidentally fire events for another users, which is a definite problem as all users access the same queue.
This is the list of solutions I have investigated, and why they won't work in my scenario
Have a non-static
DomainRaiser
class that gets passed into thePayUsing
method.- Passing a
DomainRaiser
is very difficult to do from the domain objects, sometimes events can be raised by changing properties and passing this object around is cumbersome.
- Passing a
Have all entities contain an
Events
enumeration that is read by theUnitOfWork.Commit
call as discussed on http://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/- Tracking every single entity that has possibly changed is very difficult, I am off-loading this change tracking to the ORM. This also clutters up the domain objects.
Using
HttpContext.Current.Items
to store user-specific events- This is currently the best suggestion, however, it's not possible to unit-test, and it locks my domain to using asp.net, which I have plans in the future to release a desktop app.
My question is, how do I queue and dispatch these events up in a multi-user environment reliably while taking into consideration that I only want to fire events if the overall unit-of-work succeeds?