5
\$\begingroup\$

I'm trying to implement business logic layer based on concepts of commands and command handlers.

A command is a thing that contains input parameters for executing some action, and it knows what kind of output that action should produce. A command handler contains logic for actually executing an action: it accepts a command as a param, handles it in some way, and (if successful) produces an output object.

public interface ICommand<TResultData>
{ }

public interface ICommandHandler<TCommand, TResultData>
    where TCommand : ICommand<TResultData>
{    
    CommandResult<TResultData> Handle(TCommand command);
}

public static class CommandProcessor
{
    public static CommandResult<TResultData> Process<TCommand, TResultData>(TCommand command)
        where TCommand : ICommand<TResultData>
    {
        var handler = ServiceLocator.GetInstance<ICommandHandler<TCommand, TResultData>>();
        return handler.Handle(command);
    }
}
public class CommandResult<TResultData>
{
    public bool Success { get; set; }

    public string Error { get; set; }

    public TResultData Data { get; set; }
}

Example usage:

// CreateUserCommand & handler implementations *very* simplified
public class CreateUserCommand : ICommand<User>
{
    public string Email { get; set; }
}

public class CreateUserCommandHandler : ICommandHandler<CreateUserCommand, User>
{
    private readonly IRepository repository;
    public CreateUserCommandHandler (IRepository repository)
    {
        this.repository = repository;
    }
    public CommandResult<User> Handle(CreateUserCommand command)
    {
        if(this.repository.Users.Any(u=>u.Email == command.Email)
          return new CommandResult<User> { Success = false, Error = "Email already taken"};
        var user = new User {Email = command.Email};
        this.repository.Users.Add(user);
        return new CommandResult<User> { Success = true, Data = user};
    }
}

// command usage in application code
var command = new CreateUserCommand { Email = "some.email.com" };
CommandResult<User> result = CommandProcessor.Process(command);
if(result.Success)
{
    // at this point we know that result.Data is of type User, which is nice
    // so we can use this strictly-typed result data in any way
    User createdUser = result.Data;
    Console.Writeline("Created user with Id = " + createdUser.Id);
}
else
{
    Console.Writeline("Error creating user: " + result.Error);
}

Everything works pretty nice, as you can see in the usage example, but one thing that bothers me is the empty interface ICommand. Is it a bad thing here? Can the code be refactored in some way to make it better?

\$\endgroup\$
  • \$\begingroup\$ Could you add the implementation for CreateUserCommand? Regarding your question about the ICommand interface: why does it not have this API CommandResult<TResultData> Handle(TCommand command);? It's more natural to have it there than on a separate ICommandHandler. It'd be also nice if ou could add its implementation too. \$\endgroup\$ – t3chb0t Feb 23 '18 at 20:29
  • \$\begingroup\$ @t3chb0t, I added command implementation. Command doesn't have Handle method because I want to separate input data from the logic that processes it (to be possible to inject data into handlers using DI, like IRepository in the example, or to have multiple handlers for one command) \$\endgroup\$ – Andre Borges Feb 23 '18 at 20:50
  • \$\begingroup\$ Multiple handlers for one command? This doesn't sound like a good idea. Your command is more like a command-parameter and the actual command is the handler. \$\endgroup\$ – t3chb0t Feb 23 '18 at 21:10
  • \$\begingroup\$ Well, I'm not gonna argue for/against multiple handlers (anyway, it's not implemented in my code as you can see) as it's a controversial subject indeed. But there are other reasons for separating data & logic: constructor DI possibility in handlers, avoiding fat classes (there may me dozens of props in commands and several methods in complicated handlers), etc. \$\endgroup\$ – Andre Borges Feb 23 '18 at 21:35
  • \$\begingroup\$ I'm trying to use something very similar but I get a compilation error on the .Process call, telling me the compiler cannot infer the type from usage. I have to pass both the command interface and the type of the return value for it to work, which obviously is less than ideal. Any tips? Your code as-is doesn't compile for me. \$\endgroup\$ – julealgon Jun 15 '18 at 21:58
3
\$\begingroup\$

Your marker interface has no added value. In fact, it's an anti-pattern because you provide a generic type parameter on an interface ICommand, only to enforce it on another interface ICommandResult.

public interface ICommand<TResultData>
{ }

public interface ICommandHandler<TCommand, TResultData>
    where TCommand : ICommand<TResultData>
{    
    CommandResult<TResultData> Handle(TCommand command);
}

Since specific commands have nothing in common, I would not create an interface for commands. Also, TResultData has nothing to do with a command, and everything with the command result.

The handler could be rewritten as follows:

public interface ICommandHandler<TCommand, TResultData>
{    
    CommandResult<TResultData> Handle(TCommand command);
}
\$\endgroup\$
  • 1
    \$\begingroup\$ I love marker interfaces ;-P I use them a lot ;-] When there is a pattern of using anti-patterns is this then like an anti-anti-pattern or do they cancel out and it's like I wasn't using any patterns at all? So in other words is using anti-patterns an anti-pattern itself or is using anti-patterns a normal pattern? That is the question. Philosophical saturday. \$\endgroup\$ – t3chb0t Sep 14 at 17:28
  • 1
    \$\begingroup\$ @t3chb0t that still doesn't answer the fundamental question: "If a tree falls in a forest and no one is around to hear it, does it make a sound?" o_O \$\endgroup\$ – dfhwze Sep 14 at 17:40
  • 1
    \$\begingroup\$ My grandma knew the answer to that question. She used to say that I wouldn't have eaten my breakfast because she didn't see me doing this so I should eat one now. About the tree... how does the tree know someone is around to know when to make a sound? Is it possible to surprise a tree and see it falling souldless because it didn't know you were watching? ;-| \$\endgroup\$ – t3chb0t Sep 14 at 17:46
  • 1
    \$\begingroup\$ @t3chb0t Perhaps I should have also told you the tree is from Schrödinger's forest. Oh, and breakfast is overrated anyway :/ \$\endgroup\$ – dfhwze Sep 14 at 17:54
  • 1
    \$\begingroup\$ (unrelated to the discussion above ^) @dfhwze, your implementation of ICommandHandler suggests that a command is allowed to have multiple handlers that return different types of TResultData. And the calling code will need to know what kind of handlers exist for each command, and what type of TResultData to request from those handlers. I would like to achieve something different. The calling code only needs to know the type of command it wants to call, and the type of the result should be constant for each command, and hard-coded somewhere. \$\endgroup\$ – Andre Borges Nov 8 at 10:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.