Sunday, February 3, 2013

Raising events in commandhandlers

I've explored quite a few options on how to handle commands and queries in the last few posts. I finally settled on this approach. The example used in that post looked like this.
public class CreateSubscriptionCommandHandler : ICommandHandler<CreateSubscriptionCommand>
{    
    private IDocumentSession _session;

    public CreateSubscriptionCommandHandler(IDocumentSession session)
    {
        _session = session;
    }

    public void Handle(CreateSubscriptionCommand command)
    {
        var subscription = new Documents.Subscription(
            command.Value, command.Category, command.EmailAddress);

        _session.Store(subscription);    
    }
}
Now imagine I would want to do some extra stuff after creating the subscription; update the sales statistics, append the email address to a mailing list, send out a confirmation email, etc..

You could go at this by simply extending the commandhandler, but the problem here is that you quickly end up with a bulky and dependency-heavy commandhandler, which will quickly fail to communicate its intent.

One solution could be to introduce events to decouple things in smaller pieces, and to help communicate intent more clearly.

The infrastructure to handle events is rather straightforward, and can be based on Udi Dahan's Domain Events Salvation.
public class Events : IEvents
{
    private readonly IKernel _kernel;

    public Events(IKernel kernel)
    {
        _kernel = kernel;
    }

    public void Raise<T>(T @event) where T : IEvent
    {
        var handlers = _kernel.GetAll<IEventHandler<T>>();

        foreach (var handler in handlers)        
            handler.Handle(@event);        
    }     
}
When an event is raised, the eventing infrastructure will look in the container for implementations that can handle the event, and invoke them in a random order.

Raising an event from the commandhandler can be done by injecting this extra piece of infrastructure.
public class CreateSubscriptionCommandHandler : ICommandHandler<CreateSubscriptionCommand>
{    
    private IDocumentSession _session;
    private readonly IEvents _events;

    public CreateSubscriptionCommandHandler(IDocumentSession session, IEvent events)
    {
        _session = session;
        _events = events;
    }

    public void Handle(CreateSubscriptionCommand command)
    {
        var subscription = new Documents.Subscription(
            command.Value, command.Category, command.EmailAddress);

        _session.Store(subscription);    
        
        _events.Raise(new SubscriptionCreatedEvent(query.Id));
    }
}
The SubscriptionCreatedEvent class is a simple value object, which exposes the subscription identifier.
public class SubscriptionCreatedEvent : IEvent
{
    public SubscriptionCreatedEvent(string subscriptionId)
    {
        SubscriptionId = subscriptionId;
    }

    public string SubscriptionId { get; private set; }

    public override bool Equals(Object other)
    {
        if (other == null)
            return false;

        var otherEvent = other as SubscriptionCreatedEvent;
        if (otherEvent == null)
            return false;

        return otherEvent.SubscriptionId == SubscriptionId;
    }

    public override int GetHashCode()
    {
        return SubscriptionId.GetHashCode();
    }
}   
To subscribe to this event, implement the IEventHandler interface, and register the implemenation in the container.
public interface IEventHandler<T> where T : IEvent
{
    void Handle(T @event);
}

public class SendConfirmationMailOnSubscriptionCreated : IEventHandler<SubscriptionCreatedEvent>
{    
    public void  Handle(SubscriptionCreatedEvent @event)
    {
        ...
    }
}

public class UpdateSalesStatisticsOnSubscriptionCreated : IEventHandler<SubscriptionCreatedEvent>
{    
    public void  Handle(SubscriptionCreatedEvent @event)
    {
        ...
    }
}
Eventhandlers are invoked synchronously, and participate in the commandhandler's unit of work, so if something goes haywire in one of the eventhandlers, nothing gets committed, not even what happened in the original commandhandler. Depending on your requirements, you might want to handle this differently though.

With this approach, tests also become more compact. Commandhandler tests now only need to assert that the event gets raised, and all the other logic gets offloaded to separate tests per eventhandler.

Summary

By introducing events, you can decouple commandhandlers into more focused, and intent-revealing bits. Your tests are the perfect proof of how much cleaner things get. One of the cues to listen for is when you do x or on doing y, also do z.

Are you using events? If so, domain events, or its big brother Event Sourcing?

2 comments:

  1. I disagree. There must be some place where things are orchestrated together. If I want to go in and change something, it is absolutely crucial that I can see easily and on one VS screen what is going on, in which order. Since your events are executed synchronously and are part of your UoW, they can also make a transaction rollback (which is good, i.e. email couldn't be sent). This is the reason why I think it is better to fire them explicitly, as part of the commands. Readability always wins. Stub out dependencies if you want to unit-test test.

    Btw, I think you don't need or should test your handlers. Actually, they are part of your testing infrastructure and should be as dumb as they can be, i.e. just injecting the session object and calling the Execute() method, maybe adding logging or re-try haviour.

    ReplyDelete
    Replies
    1. I disagree. There must be some place where things are orchestrated together. If I want to go in and change something, it is absolutely crucial that I can see easily and on one VS screen what is going on, in which order. Since your events are executed synchronously and are part of your UoW, they can also make a transaction rollback (which is good, i.e. email couldn't be sent). This is the reason why I think it is better to fire them explicitly, as part of the commands. Readability always wins. Stub out dependencies if you want to unit-test test.

      > I think it makes sense to model commands and their side effects as separate concepts; a pure command, which fires an event, which can get handled by eventhandlers.
      The events live in the same namespace/folder, so that's close enough for me. It's a bit like saying you shouldn't let your container inject implementations at runtime, because you aren't aware of the actual implementation at design time. You really shouldn't care in which order the events get executed; eventhandlers shouldn't impact eachother.

      Btw, I think you don't need or should test your handlers. Actually, they are part of your testing infrastructure and should be as dumb as they can be, i.e. just injecting the session object and calling the Execute() method, maybe adding logging or re-try haviour.

      > The handlers contain all the logic. The infrastructural logic you're referring to lives in the bus (http://www.jefclaes.be/2013/01/separating-command-data-from-logic-and.html).

      Delete