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?
CreateUserCommand? Regarding your question about theICommandinterface: 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\$.Processcall, 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\$