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?
1 Answer 1
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);
}
-
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– t3chb0t2019年09月14日 17:28:31 +00:00Commented Sep 14, 2019 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– dfhwze2019年09月14日 17:40:06 +00:00Commented Sep 14, 2019 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– t3chb0t2019年09月14日 17:46:52 +00:00Commented Sep 14, 2019 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– dfhwze2019年09月14日 17:54:46 +00:00Commented Sep 14, 2019 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 ofTResultData
. And the calling code will need to know what kind of handlers exist for each command, and what type ofTResultData
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– Andre Borges2019年11月08日 10:08:06 +00:00Commented Nov 8, 2019 at 10:08
Explore related questions
See similar questions with these tags.
CreateUserCommand
? Regarding your question about theICommand
interface: why does it not have this APICommandResult<TResultData> Handle(TCommand command);
? It's more natural to have it there than on a separateICommandHandler
. It'd be also nice if ou could add its implementation too. \$\endgroup\$.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\$