DI с шаблоном команды

1

У меня есть служба, которая действует для создания и отправки команд для выполнения. Он может либо записывать команды в очередь для отложенной обработки, либо выполнять их немедленно.

Для этих целей у меня есть что-то вроде:

class MyService
{
    private ICommandQueueWriter _commandQueueWriter;
    private ICommandExecutor _commandExecutor;

    public MyService(ICommandQueueWriter cqw, ICommandExecutor ce)
    {
        _commandQueueWriter = cqr;
        _commandExecutor = ce;
    }

    public void DoSomething()
    {
        _commandQueueWriter.Write(new SomeCommand());
        _commandExecutor.Execute(new SomeOtherCommand());
    }
}

Услуга будет работать со всеми командами. У меня есть серия реализаций ICommandHandler<> которые будут зарегистрированы в контейнере DI.

В моем плане есть недостаток: реализация ICommandExecutor должна иметь доступ ко всем реализациям ICommandHandler<>. На самом деле, я думаю, что выполнение очереди в очереди будет иметь ту же проблему: я захвачу сообщение позже и должен каким-то образом найти обработчик.

Поэтому мои варианты, которые я вижу, это:

  1. Не используйте ICommandExecutor в качестве зависимости, просто используйте ICommandHandler<> напрямую. Но мне нужен вариант обертывания всего выполнения обработчика команд через стандартный класс - для последовательного отслеживания исключений или для управления выполнением каким-либо другим последовательным способом. Мне очень нравится возможность иметь последовательный интерфейс для немедленного/отсроченного исполнения (либо вызвать Write или Execute)
  2. Передайте контейнер DI или корень в ICommandExecutor и разрешите ему разрешать команды. Это, по-видимому, нарушает идею о том, что должен быть один вызов для составления графа объектов с DI и может "скрывать" зависимости
  3. Внедрение ICommandExecutor имеет все ICommandHandler<> в качестве зависимости, которую нужно ввести, чтобы он мог выбрать тот, который он хочет вручную. Однако это тоже не идеально, поскольку все обработчики в системе будут созданы в этой точке

Есть ли четвертый вариант или мне нужно укусить пулю одним из этих компромиссов?

  • 0
    Я не совсем вижу проблему. Как выглядит ваша реализация ICommandExecutor настоящее время? Что такое ICommandHandler<> и как он используется. Показ реализации ICommandExecutor поможет, потому что вы говорите, что проблема есть.
  • 0
    Это концептуально в настоящее время. Проблема заключается в том, что ICommandExecutor должен разрешить ICommandHandler <> для своей реализации, но он не знает заранее, какие типы команд необходимо обрабатывать. Это концептуальная проблема DI. Кроме того, я думал, что это само собой разумеется, но ICommandHandler <> является универсальным интерфейсом для обработчиков команд ..?
Показать ещё 1 комментарий
Теги:
dependency-injection

2 ответа

3

Мне кажется, что использование как ICommandQueueWriter и ICommandExecutor кажется странным. Почему потребитель (MyService в вашем случае) должен знать, что одна команда поставлена в очередь, а другая - напрямую. Я думаю, что это должно быть прозрачным.

Внедрение ICommandExecutor имеет все ICommandHandler <> s в качестве зависимости, которую нужно ввести

Это вызовет серьезные проблемы с обслуживанием, потому что вы будете добавлять новые обработчики команд очень регулярно, и это заставит вас каждый раз обновлять конструктор исполнителей команд.

Хотя вы также можете ввести коллекцию обработчиков команд, это все равно заставит вас перебирать список каждый раз, когда вы хотите его выполнить, чтобы получить правильную реализацию. С течением времени это будет медленнее, потому что вы будете регулярно добавлять новые обработчики команд.

Передайте контейнер DI или корень в ICommandExecutor и разрешите ему разрешать команды. Это, по-видимому, нарушает идею о том, что должен быть один вызов для составления графа объектов с DI и может "скрывать" зависимости

Может показаться, что вы применяете анти-шаблон Service Locator, если вы это делаете, но это только в случае, если ICommandExecutor является частью кода приложения. Хитрость заключается в том, чтобы сделать ICommandExecutor частью вашего корня композиции. Это решает проблему, потому что корневой состав уже будет очень тесно связан с вашим контейнером.

  • 0
    Спасибо, очень полезно, и это вроде хорошо сидит и ставит официальное название чему-то, что было мне немного скользко. Заинтересованы в быстром продолжении вашего первого пункта - когда вы говорите «клиент», вы имеете в виду услугу? В этом случае служба планируется как фасад для абстрагирования использования команд и способа их отправки. Мое мышление: один сервисный вызов может отправить 20 000 уведомлений по электронной почте, другой может привести к обновлению одного поля, требующего подтверждения до того, как абонент возвратится. Вы бы предложили обе немедленные команды, одна команда, откладывающая работу другим способом?
  • 0
    Я снова читаю, понимаю, что вы имеете в виду, когда клиент должен знать. Я бы сказал, что им не нужно знать: они просто работают с API, и система будет обрабатывать эти детали. Не могу сразу вспомнить пример, где это падает? - но приветствовал бы один
Показать ещё 2 комментария
0

Внедрение ICommandExecutor имеет все ICommandHandler <> s в качестве вложенной зависимости, поэтому он может выбрать тот, который ему нужен вручную. Однако это тоже не идеально, поскольку все обработчики в системе будут созданы в этой точке

Если вы хотите вводить обработчики, но не хотите, чтобы обработчики были созданы во время инъекции, тогда

  1. Внесите в исполнитель команды коллекцию ленивых обработчиков-экземпляров с метаданными, содержащими тип команды.

Подобно,

IEnumerable(of Lazy(of ICommandHandler, ICommandHandlerMetadata))

где ICommandExecutorMetadata содержит тип команды, соответствующий командному обработчику

public interface ICommandHandlerMetadata
    readonly property CommandType as Type
end interface
  1. В исполнителе команд преобразование коллекции в словарь пар (Type, Lazy(of ICommandHandler, ICommandHandlerMetadata)), где Type - это тип команды, полученный из метаданных через Lazy.Metadata.CommandType.

  2. В методе CommandExecutor.Execute ленивый инициированный обработчик команд получается из словаря по типу команды. Эта операция очень быстрая. Фактический CommandHandler получается через Lazy.Value.

Фактический CommandHandler только один раз и точно, когда выполняется соответствующая команда.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню