Simple Domain Events

This is my first attempt at the domain event pattern, so use at your own risk. A lot of this post was blatantly ripped off from inspired by Udi Dahan’s posts on the same subject. Other bits and pieces come from around the ‘net. Sorry I’m not giving credit. At the time, I was researching a problem, not a blog post.

Your Problem

Let’s use the ever-popular “preferred customer” example. You’ve built this beautiful ecommerce application. You even implemented coupon code discounts and stuff. Customers put items in the carts, checkout, and magically get their stuff in 2 to 93.5 days.

Today, the sales director emails you about their new “Preferred customer” program. A preferred customer gets an automatic 2% discount on orders over $1000 (up to $20). This will be a piece of cake. You quickly write up a few new tests, slap an IsPreferred boolean property on your customer entity, and build a new order total strategy for this discount scenario. All the tests turn green.

Then you naively email him to ask how a customer becomes “preferred.” The sales director replies “After the 20th order over $1000, the customer automatically becomes preferred.” Simple enough. He continues “When that happens, we need to place an order for a complimentary gift basket and the sales rep needs an email about it. Also, any customer with a single order over $20,000 or two orders over $10,000 is automatically enrolled in the program. Next Tuesday, we’re meeting to discuss the new Gold preferred program. I want you there.”

Let’s step back and analyze what just happened here. Your beautiful application just got owned by Yet Another Tool With an MBA and you have to share oxygen with him for an hour next Tuesday. More importantly, you have a problem. Even though there are multiple ways to become a preferred customer, the customer only becomes preferred once. They only get one gift basket. Also, you need to do two entirely different actions based on the same event – place a gift basket order and send an email to the sales rep. On top of all that, there’s another mess of weird requirements in the works. How will you handle this without turning your beautiful application in to a pile of ugly hacks? Domain events.

What’s is it?

The domain events pattern is the code equivalent of the office gossip network. Whenever something interesting happens, the office gossip tells anyone and everyone who might care about the news.

You have a requirement: “When X happens, do Y1, Y2, Y3, and Y4.” X is the event. The Y’s are the resulting actions that your application should take. There may be one action. There may be 15. There may be none. It doesn’t matter really. Let’s say the event is “John B. Customer just ordered 2 widgets and a sprocket.” Obviously, one of your actions will be “Ship 2 widgets and a sprocket to John B. Customer” You’ll also want to email him a receipt of his order. If this is his first order, you’ll want to mail him a catalog and add him to the mailing list.

How would you handle this? Well, you could have your customer’s PlaceOrder method call the services directly. That creates all sorts of tight coupling that you don’t want. Plus the reference is going the wrong direction. You could inject the services and program to an interface, but a lot of people consider that a bad idea. Even with an interface, your entities know more than they need to about your services.

You’ll probably want to use domain events for this. Domain events are especially helpful when X could happen in many different places, or the Y’s change a lot. They keep your coupling down by keeping your domain blissfully ignorant.

The checkout code of your ecommerce site doesn’t need to know how to ship products, email receipts, or anything else. It just needs to tell some central event dispatcher – the office gossip - “Hey. John B. Customer just placed an order for 2 sprockets and a widget.”

What’s the solution?

Basically, if you understand Pub/Sub service bus, toss out the queue and the transactional stuff. Now you understand the layout of domain events.

You have a dispatcher. So that it’s easy to access from anywhere in your application, it’s a static class.

You also have some service that wants to know when some event happens. It tells the central dispatcher “Tell me when [particular type of event] happens.” It registers as a handler of that particular type of event.

Your domain tells the dispatcher “Hey! This just happened.” The dispatcher looks up the handler or handlers for that event and passes along the message. The handler(s) do some work based on the details of that event.

Another Trio

All of the really cool patterns have 3 parts… and singleton has thick glasses and bad acne.

  1. Events – CustomerBecomesPreferred, CustomerPlacedOrder, etc. Each of these classes will be immutable – they can’t be changed once they’re created. Even though we don’t have any common members, all of our domain events play a particular role in the application. Like Udi says in his post, define role explicitly. All of our event types will implement IDomainEvent.
  2. Handlers - In Pub/Sub, these would be the subscribers. You can have multiple handlers for a single type of event. The order that these handlers execute is unknown. For this reason, your events should be immutable. Altering the state of an event during the execution of a handler could create unexpected side-effects in a subsequent handler, and the whole thing becomes a game of Chinese Whispers. Since handlers also play a role, we’ll have an interface for them. Each handler will implement IHandle<T> where T is the type of event to be handled. We’ll also define a void Handle(T Event); method so all our handlers have a common entry point for the dispatcher.
  3. The dispatcher dispatches events to the various handlers. The internals can be implemented many different ways, but the result is the same. All of the event handlers are registered in the dispatcher. When the dispatcher raises an event, it gets all of the handlers for that type of event. One by one, it executes each handler’s Handle method. We’ll use a service locator internally. Ninject people will need to use v2.0.

Here’s the diagram:

Here’s the code:

namespace DomainEventsSample
{
    public interface IDomainEvent
    {
    }
}


namespace DomainEventsSample
{
    public interface IHandle<T> where T:IDomainEvent 
    {
        void Handle(T Event);
    }
}


using Microsoft.Practices.ServiceLocation;

namespace DomainEventsSample
{
    public static class Dispatcher
    {

        public static void Initialize(IServiceLocator ServiceLocator)
        {
            serviceLocator = ServiceLocator;
        }

        private static IServiceLocator serviceLocator;

        static void Raise<T>(T Event) where T : IDomainEvent
        {
            var handlers = serviceLocator.GetAllInstances<IHandle<T>>();
            foreach (var handler in handlers) 
            {
                handler.Handle(Event);
            }
        }

    }
}

In our main program, we bind some event handlers to their implementations, then make a customer order a bunch of stuff.

using System;
using Ninject;
using Microsoft.Practices.ServiceLocation;
using CommonServiceLocator.NinjectAdapter;

namespace DomainEventsSample
{
    class Program
    {
        static void Main(string[] args)
        {
            IKernel kernel = new StandardKernel();
            IServiceLocator sl = new NinjectServiceLocator(kernel);
            Dispatcher.Initialize(sl);

            kernel.Bind<IHandle<CustomerPlacedOrderEvent>>().To<ShipOrder>();
            kernel.Bind<IHandle<CustomerBecamePreferred>>().To<SendPreferredGiftBasket>();
            kernel.Bind<IHandle<CustomerBecamePreferred>>().To<SendMessageToSalesRep>();

            Customer c = new Customer();
            for (var i = 0; i < 22; i++)
            {
                Order newOrder = new Order(1000.00 + i);
                c.PlaceOrder(newOrder);
                Console.WriteLine("------------------------------------");
            }
            Console.ReadLine();

        }
    }
}

Our events are pretty simple. They just pass along the relevant information. When the event is about something that happened to a customer, we need to know which customer. When the event is about an order, we need to know which order.

namespace DomainEventsSample
{
    public class CustomerPlacedOrder:IDomainEvent 
    {

        public CustomerPlacedOrder(Order Order)
        {
            order = Order;
        }

        private readonly Order order;

        public Order Order
        {
            get
            {
                return order;
            }
        }

    }
}

Here’s a handler. Of course, this is just a stub for some real action you would implement.

using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DomainEventsSample
{
    public class ShipOrder : IHandle<CustomerPlacedOrderEvent>
    {
        #region IHandle<CustomerPlacedOrderEvent> Members

        void IHandle<CustomerPlacedOrderEvent>.Handle(CustomerPlacedOrderEvent Event)
        {
            Console.WriteLine("Shipping order totalling {0:C}",Event.Order.Total);
        }

        #endregion
    }
}

Bidirectional Communication

This is the real reason I’m posting. I had a situation where a domain event handler called in to another system to do some action and the log of that action needed to make its way back to the UI.

Just like with Pub/Sub, domain events are one way. I didn’t want to give up domain events, but I had a requirement I couldn’t fill with the pattern. So, after hacking at it for an afternoon and getting nowhere, I sent out a call for help. Ayende wrote me back.

Publish a new event that the UI is listening to

He explains in 10 words what takes me 3 paragraphs. There’s your answer folks. When your handler needs to respond, publish a response event. Simple. Elegant.

Gimme the Code!

I’m putting all of the code for this blog in SVN at http://basiclyeverything.googlecode.com/svn/trunk/

The code for this particular post is in http://basiclyeverything.googlecode.com/svn/trunk/DomainEventsSample

blog comments powered by Disqus