Take a look at this nasty code:
[HttpPost, UnitOfWork] public ActionResult Confirm(Order order) { order.OrderStatus = OrderStatus.Created; if(order.ContactMe) { var mailingListSubscription = new MailingListSubscription { Contact = order.PostalContact, Email = order.Email, DateSubscribed = DateTime.Now }; mailingListRepository.SaveOrUpdate(mailingListSubscription); } EmailOrder(order); basketService.CreateNewBasketFor(userService.CurrentUser); return this.RedirectToAction<OrderController>(c => c.Item(order.Id)); }
This is a controller action from Suteki Shop that is fired when the user confirms an order. There is no separation of concerns (SOC) here. In a single action method we change the status of the order, check to see if the user should be added to our mailing list, email the order confirmation, and finally create a new basket for the user. What we should really be doing here is simply asking the order to update its status:
[HttpPost, UnitOfWork] public ActionResult Confirm(Order order) { userService.CurrentUser.EnsureCanView(order); order.Confirm(); return this.RedirectToAction<OrderController>(c => c.Item(order.Id)); }
This is much nicer. But the actions that occur as a result of the order confirmation still need to happen. We have a dilemma now, good design says we shouldn’t inject services into our domain entities (Order in this case), but in order to update the mailing list, send an email and create a new basket, we have no choice but to invoke those domain services. Even if we could access domain services from our entities, it would be another SOC violation to do it. Why should the order confirm method have to know about all the things that need to happen when an order is confirmed?
A really nice answer to this kind of issue is Udi Dahan’s Domain Event pattern. This pattern allows domain entities to raise events without having to be aware of what the events will trigger. So the Order.Confirm() method looks like this:
public virtual void Confirm() { OrderStatus = OrderStatus.Created; DomainEvent.Raise(new OrderConfirmed(this)); }
It simply alters its state as required, in this case setting its status to Created and then raises an event to tell the world what’s happened.
Events can be handled by zero or more handlers. Handlers implement a simple interface:
public interface IHandle<TEvent> where TEvent : class, IDomainEvent { void Handle(TEvent @event); }
Here’s the AddToMailingListOnOrderConfirmed handler that adds the user to our mailing list:
public class AddToMailingListOnOrderConfirmed : IHandle<OrderConfirmed> { readonly IRepository<MailingListSubscription> mailingListRepository; public AddToMailingListOnOrderConfirmed(IRepository<MailingListSubscription> mailingListRepository) { this.mailingListRepository = mailingListRepository; } public void Handle(OrderConfirmed orderConfirmed) { if (orderConfirmed == null) { throw new ArgumentNullException("orderConfirmed"); } var order = orderConfirmed.Order; if (!order.ContactMe) return; var mailingListSubscription = new MailingListSubscription { Contact = order.PostalContact, Email = order.Email, DateSubscribed = DateTime.Now }; mailingListRepository.SaveOrUpdate(mailingListSubscription); } }
We simply implement the IHandle interface for a specific event, in this case OrderConfirmed. Because the handler is supplied by our IoC container, we can specify any services we require for the handler to do its job, in this case we require an IRepository<MailingListSubscription> in order for the handler to do its job.
There are similar handlers to send the confirmation email and create the new basket.
I’m not going to repeat Udi’s whole post here. Go and read it to see how the DomainEvent class is implemented. However I will repeat a crucial point: this is not about asynchronous messaging systems like MSMQ. The event pattern is simply a way to structure our software. When the order.Confirm() method is called, all the event handlers will fire synchronously before it returns.
Now there’s nothing to stop you from implementing an event handler that publishes an event onto an NServiceBus or MassTransit messaging system. Indeed, implementing domain events gives you the perfect hook for scaling your application as required by doing just that at a later date.
I really like this pattern, but there tend to be two concerns that people have when they encounter it:
- Static methods suck! How can I test a static method buried in my domain entity? Yes, it’s not good OO practice to use static methods, but I think for certain infrastructure concerns, it’s OK. Here we have a single static method that can allow us to do some very nice refactoring, so I believe it’s a good payoff. Testability is not really an issue if you implement the DomainEvent class correctly.
- It’s not clear what exactly is going to happen when I call order.Confirm(). Yes there is a level of indirection here, but that is a good thing. You can see that a domain event is being raised, and it’s pretty easy to find all the handlers, so I'd argue that it’s not really a problem.
If you want to see more detail, you can browse my commit for this change in the Suteki Shop source code: http://code.google.com/p/sutekishop/source/detail?r=415