Brownfield CQRS part 2 – Command Handlers

Spread the love

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.

  • Brownfield CQRS part 1 – Commands
  • Brownfield CQRS part 2 – Command Handlers
  • Brownfield CQRS part 3 – Queries, Parameters and Results
  • Brownfield CQRS part 4 – Command Dispatcher

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.