Brownfield CQRS part 2 – Command Handlers

In my previous post, I described command DTOs and service methods for booking a table at a restaurant. Now, we just need something to interpret this command, and do something useful with it.

To do this, we create a corresponding command handler for each command:

public interface ICommandHandler where T : ICommand
{
    void Handle(T command);
}

Command handlers are responsible for:

  • Performing any required validation on the command.
  • Invoking the domain — coordinating objects in the domain, and invoking the appropriate behaviour on them.

Command handlers are application services, and each execution represents a separate unit of work (e.g. a database transaction). There is only one command handler per command, because commands can only be handled once — they are not broadcast out to all interested listeners like event handlers.

Here’s an example for handling our BookTableCommand. A one-to-one handler/command mapping makes it easy to add/remove features from our service.

public class BookTableCommandHandler : ICommandHandler<BookTableCommand>
{
    IDinnerServiceRepository nights;

    public void Handle(BookTableCommand command)
    {
        var dinnerService = nights[command.TimeAndDay];
        var party = new Party(command.PartySize, command.PartyName);
        night.TakeBooking(party);
    }
}

Note each command implements ICommand — a simple explicit role marker interface that also allows us to use constraints on generic types and automate IoC registration of command handlers.

public interface ICommand { }

Command validation and errors

Aside from transient technical faults, there are two business reasons a command might fail:

  • The command was not valid — e.g. you tried to book a table for zero people.
  • The command could not succeed — e.g. the restaurant is fully booked that night.

Ideally, the client will have pre-checked these to save time, but if the command handler detects a problem, how do we report it back to the user, given commands are not allowed to have return values? How would we report success even?

Actually, this is not a problem at all — commands have no return value, but they can throw a detailed validation/command failed exception back to the client. If they didn’t throw anything, it is assumed to have succeeded.

What if you execute commands asynchronously — e.g. queued and executed at some later time? We can’t throw an exception back to the client in this case. But that’s fine — the client must always assume their asynchronous command will succeed. If it does fail, it will be reported back through some alternate channel (e.g. via e-mail or the query side). This is why it is important to pre-validate commands on the client as much as possible.

Next: Part 3 – Queries, Parameters and Results

June 16, 2010

7 Comments

seagile on June 16, 2010 at 11:32 am.

I know Greg and Udi in particular are very keen on implementing asynchronous commands, but if part of the workflow is getting a confirmation and printing a letter you can hand over to e.g. a patient (so he knows when his appointment is due), then “e-mail” just isn’t good enough of a feedback mechanism. I don’t want to give the patient a call saying his appointment didn’t make into the system because somebody else is already attending at that particular point in time (== the concurrency exception we got in the system asynchronously).
It doesn’t mean the command can’t be executed asynchronously from a technical point of view (I could wait in my UI for a callback/timeout message), but it does mean that this particular workflow is inherently request-response.
What I’m trying to say is that you shouldn’t take a blind leap of faith on the whole asynchronous command thing. Depending on how crucial the user feedback is (the domain expert should know) you’ll be able to choose from request/response or asynchronous processing. Choosing the wrong one could disrupt the user’s workflow and create havoc in your system.

seagile on June 16, 2010 at 11:37 am.

Sidenote: I’m aware of compensating commands/actions, but they suffer the same problem … They are just too late in the feedback cycle for this particular problem.

To be very clear: everything I’m saying here is depending on the context I’m working in. Your situation might differ.

Richard on June 16, 2010 at 11:47 am.

Seagile: I would use a synchronous endpoint in situations like this. Same as example of withdrawing cash at a cash machine.

Kevin Berridge on July 6, 2010 at 8:48 pm.

Where does persistence happen? Typically in DDD the domain doesn’t persist itself, but it looks like in this example night.TakeBooking(party); must be performing whatever persistence is needed. Is that correct?

Also, I couldn’t tell from your series of posts if you have a separate data store for your commands (event sourcing) than for your queries. Could you clarify that?

Thanks,
Kevin

Richard on July 7, 2010 at 10:28 am.

Kevin: these posts don’t go into persistence strategies — first we just need to get the external interfaces right, then you can implement the service internals using full-blown event sourcing etc.

Leave Your Comment

Your email will not be published or shared. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>