Saturday, 9 March 2019

Command Handling Pattern Part 3

A Command Dispatcher

 

The approach outlined in the previous post is fine, however there are a few limitations. One is that clients that need to issue several commands will have a proliferation of dependencies. For example, even in a simple CRUD application, a controller for a particular entity will likely have to Create, Update and Delete instances of that entity. Each of these operations will be a command, so the controller will need a handler for each command. For example:
public class CustomersController : Controller
{
    private readonly ICommandHandler<AddCustomer> _addCustomerHandler;
    private readonly ICommandHandler<EditCustomer> _editCustomerHandler;
    private readonly ICommandHandler<DeleteCustomer> _deleteCustomerHandler;

    public CustomersController(
        ICommandHandler<AddCustomer> _addCustomerHandler,
        ICommandHandler<EditCustomer> _editCustomerHandler,
        ICommandHandler<DeleteCustomer> _deleteCustomerHandler)
    {
        // assign fields
    }

    // More code here
}

This can quickly get out of hand, and when looking at the dependencies for a particular class it's enough to know that they issue commands without knowing the specific commands that are issued. It's usually clear from the context which entity is being operated on anyway.

So a single class that can accept any command and dispatch it to the appropriate handler seemed like a good idea. That would make the above controller code much more concise:
public class CustomersController : Controller
{
    private readonly ICommandDispatcher _commandDispatcher;

    public CustomersController(ICommandDispatcher commandDispatcher)
    {
        _commandDispatcher = commandDispatcher;
    }

    // More code here
}

Here is an initial implementation of the command dispatcher itself. Note it uses the application's IoC container to get the correct command handler (I'm using the IServiceProvider that comes with AspNetCore). This may be an anti-pattern to some (Service Locator anyone?) but the dispatcher needs to be able to instantiate any handler in the system.
public class CommandDispatcher : ICommandDispatcher
{
    private readonly IServiceProvider _serviceProvider;

    public CommandDispatcher(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public async Task<CommandResult> ExecuteAsync<T>(T command)
    {
        var handler = _serviceProvider.GetService<ICommandHandler<T>>();
        // Null checks

        return await handler.HandleAsync(command);
    }
}

The dispatcher simply gets the required handler from the IoC container and then passes it the command to be handled. If this was all it did then I probably would class it as an anti-pattern and consign it to the dustbin. However now that all clients will depend on the dispatcher rather than individual handlers, it means that additional processing can be introduced outside of the handler and be run with every request. This is particularly useful for cross-cutting concerns like logging and exception handling, and for me is the major attraction of this approach. I'll spend the next couple of posts going through some examples.

No comments:

Post a Comment