Зависимость разрешения автозаполнения в CQRS CommandDispatcher

Я пытаюсь реализовать простой пример CQRS-приложения.

Это структура моей части "Command":

public interface ICommand
{
}

//base interface for command handlers
interface ICommandHandler<in TCommand> where TCommand: ICommand
{
    void Execute(TCommand command);
}

// example of the command
public class SimpleCommand: ICommand 
{
   //some properties
}

// example of the SimpleCommand command handler
public class SimpleCommandHandler: ICommandHandler<SimpleCommand>
{
    public void Execute(SimpleCommand command)
    {
       //some logic
    }

}

Это интерфейс ICommandDipatcher. Он отправляет команду своему обработчику.

public interface ICommandDispatcher
{
    void Dispatch<TCommand>(TCommand command) where TCommand : ICommand;
}

Это стандартная реализация ICommandDispatcher, и основной проблемой является получение необходимого обработчика команд по типу команды через Autofac.

public class DefaultCommandDispatcher : ICommandDispatcher
{
    public void Dispatch<TCommand>(TCommand command) where TCommand : ICommand
    {
        //How to resolve/get object of the neseccary command handler 
        //by the type of command (TCommand)
        handler.Execute(command);
    }
}

Каков наилучший способ разрешить реализацию ICommandHanler по типу команды в этом случае с помощью Autofac?

Спасибо!

Ответ 1

С помощью Autofac вам нужно ввести IComponentContext в диспетчер. Таким образом вы можете вернуться в контейнер для разрешения требуемого обработчика команд:

public class AutofacCommandDispatcher : ICommandDispatcher
{
    private readonly IComponentContext context;

    public AutofacCommandDispatcher(IComponentContext context)
    {
        this.context = context;
    }

    public void Dispatch<TCommand>(TCommand command)
    {
        var handler = this.context.Resolve<ICommandHandler<TCommand>>();

        void handler.Execute(command);
    }
}

Вы можете зарегистрировать AutofacCommandDispatcher следующим образом:

builder.RegisterType<AutofacCommandDispatcher>().As<ICommandDispatcher>();

И вы можете зарегистрировать все свои обработчики команд за один раз следующим образом:

builder.RegisterAssemblyTypes(myAssembly)
    .AsClosedTypesOf(typeof(ICommandHandler<>));

Две заметки. Прежде всего, вы, вероятно, определили ICommandHandler<T> как контравариантный (с ключевым словом in), потому что Resharper так сказал, но это плохая идея для обработчиков команд. Всегда существует сопоставление "один к одному" между командным и командным обработчиком, но определение ключевого слова in сообщает, что может быть несколько реализаций.

Во-вторых, на мой взгляд, наличие диспетчера команд - плохая идея, потому что это может скрыть тот факт, что у потребляющих классов обработчиков команд слишком много зависимостей, что является признаком нарушения принципа единой ответственности. Более того, использование такого диспетчера откладывает создание части графического объекта (части обработчика команд) до тех пор, пока фактически не будет выполнена команда (против того, когда потребитель будет разрешен). Это затрудняет проверку конфигурации контейнера. Когда обработчики команд вводятся напрямую, вы точно знаете, что весь граф объекта можно разрешить, когда можно разрешить типы корней в вашей конфигурации. Легко определить команду, но забудьте создать соответствующий обработчик команд, поэтому вам нужно будет добавить для этого единичные тесты, чтобы проверить, имеет ли каждая команда соответствующий обработчик. Вы можете избавиться от необходимости писать такой тест, если вы удалите диспетчера вместе.

Ответ 2

Предполагая, что у вас есть ConcreteCommand : IComman и ConcreteCommandHandler : ICommandHandler<ConcreteCommand>, используйте метод RegisterType следующим образом:

builder.RegisterType<ConcreteCommandHandler>()
       .As<ICommandHandler<ConcreteCommand>>();

И затем введите ваш обработчик:

private ICommandHandler<ConcreteCommand> concreteCommandHandler;

Также посмотрите автоматическую регистрацию типов сборки Возможности Autofac.

Если вы хотите разрешить реализацию ICommandHandler ICommand, то может помочь регистрация factory. Зарегистрируйте Func<Type, ICommandHandler> или определите специальный класс, который разрешит соответствующий обработчик команд.