Configure decorators for generic interfaces and inject all instances to constructor with non generic interface argument in Simple Injector

五迷三道 提交于 2019-12-03 08:56:15

This will reduce the number of parameters that the constructors need to take and make the whole thing a little easier to use

Please take a close watch on this, because by doing this, you might hide the fact that your controllers do too much; violate the Single Responsibility Principle. SRP violations tend to lead to maintainability issues later on. There's even a follow up article of the author of the article you refer to (that's me btw) that states:

I certainly wouldn’t advocate an ICommandProcessor [that's ICommandAndQueryDispatcher in your case] for executing commands - consumers are less likely to take a dependency on many command handlers and if they do it would probably be a violation of SRP. (source)

The article even discusses a solution for this for queries, but you can apply it to your commands as well. But you should consider stripping your solution down and remove the non-generic interfaces. You don't need them.

Instead, define the following:

public interface ICommandHandler<TCommand> : where TCommand : ICommand
{
    void Execute(TCommand command);
}

Do note a few things:

  1. There's no non-generic interface.
  2. You don't pass through the ICommandAndQueryDispatcher. That's just ugly. The ICommandAndQueryDispatcher is a service and services need to be passed through constructor injection. The command on the other hand is runtime data, and runtime data is passed through using method arguments. So if there's a command or query handler that needs the dispatcher: inject it through the constructor.
  3. There's no in keyword for TCommand. Since commands are use cases, there should be a one-to-one mapping between a command and a command handler implementation. Specifying an 'in' however means that one command class can map to multiple handlers, but this should not be the case. When dealing with events and event handlers on the other hand, this will be a much more obvious approach.
  4. No more CommandHandlerBase<TCommand>. You don't need that. I would argue that a good design hardly ever needs a base class.

Another thing, don't try to mix the dispatcher for commands with that for the queries. Two responsibilities means two classes. This is how your command dispatcher will look:

// This interface is defined in a core application layer
public interface ICommandDispatcher
{
    void Execute(ICommand command);
}

// This class is defined in your Composition Root (where you wire your container)
// It needs a dependency to the container.
sealed class CommandDispatcher : ICommandDispatcher
{
    private readonly Container container;

    public CommandDispatcher(Container container) {
        this.container = container;
    }

    public void Execute(ICommand command) {
        var handlerType = typeof(ICommandHandler<>)
            .MakeGenericType(command.GetType());

        dynamic handler = container.GetInstance(handlerType);

        handler.Handle((dynamic)command);
    }
}

Do note that you don't inject a collection of command handlers here, but instead to request a handler from the container. This code will only contain infrastructure and no business logic, so if you place this implementation close to the code that is responsible of wiring the container, you will not abuse the Service Locator anti-pattern, and this is a valid approach. The rest of the application in that case still doesn't depend on the DI framework.

You can register this CommandDispatcher as follows:

container.RegisterSingle<ICommandDispatcher>(new CommandDispatcher(container));

If you take this approach, because you request a handler by the ICommandHandler<TCommand> interface, the container will automatically wrap the handlers with any decorators that must be applied according to your configuration and the generic type constraints you applied.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!