У меня есть служба, которая действует для создания и отправки команд для выполнения. Он может либо записывать команды в очередь для отложенной обработки, либо выполнять их немедленно.
Для этих целей у меня есть что-то вроде:
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<>
. На самом деле, я думаю, что выполнение очереди в очереди будет иметь ту же проблему: я захвачу сообщение позже и должен каким-то образом найти обработчик.
Поэтому мои варианты, которые я вижу, это:
ICommandExecutor
в качестве зависимости, просто используйте ICommandHandler<>
напрямую. Но мне нужен вариант обертывания всего выполнения обработчика команд через стандартный класс - для последовательного отслеживания исключений или для управления выполнением каким-либо другим последовательным способом. Мне очень нравится возможность иметь последовательный интерфейс для немедленного/отсроченного исполнения (либо вызвать Write или Execute)ICommandExecutor
и разрешите ему разрешать команды. Это, по-видимому, нарушает идею о том, что должен быть один вызов для составления графа объектов с DI и может "скрывать" зависимостиICommandExecutor
имеет все ICommandHandler<>
в качестве зависимости, которую нужно ввести, чтобы он мог выбрать тот, который он хочет вручную. Однако это тоже не идеально, поскольку все обработчики в системе будут созданы в этой точкеЕсть ли четвертый вариант или мне нужно укусить пулю одним из этих компромиссов?
Мне кажется, что использование как ICommandQueueWriter
и ICommandExecutor
кажется странным. Почему потребитель (MyService
в вашем случае) должен знать, что одна команда поставлена в очередь, а другая - напрямую. Я думаю, что это должно быть прозрачным.
Внедрение ICommandExecutor имеет все ICommandHandler <> s в качестве зависимости, которую нужно ввести
Это вызовет серьезные проблемы с обслуживанием, потому что вы будете добавлять новые обработчики команд очень регулярно, и это заставит вас каждый раз обновлять конструктор исполнителей команд.
Хотя вы также можете ввести коллекцию обработчиков команд, это все равно заставит вас перебирать список каждый раз, когда вы хотите его выполнить, чтобы получить правильную реализацию. С течением времени это будет медленнее, потому что вы будете регулярно добавлять новые обработчики команд.
Передайте контейнер DI или корень в ICommandExecutor и разрешите ему разрешать команды. Это, по-видимому, нарушает идею о том, что должен быть один вызов для составления графа объектов с DI и может "скрывать" зависимости
Может показаться, что вы применяете анти-шаблон Service Locator, если вы это делаете, но это только в случае, если ICommandExecutor
является частью кода приложения. Хитрость заключается в том, чтобы сделать ICommandExecutor
частью вашего корня композиции. Это решает проблему, потому что корневой состав уже будет очень тесно связан с вашим контейнером.
Внедрение ICommandExecutor имеет все ICommandHandler <> s в качестве вложенной зависимости, поэтому он может выбрать тот, который ему нужен вручную. Однако это тоже не идеально, поскольку все обработчики в системе будут созданы в этой точке
Если вы хотите вводить обработчики, но не хотите, чтобы обработчики были созданы во время инъекции, тогда
Подобно,
IEnumerable(of Lazy(of ICommandHandler, ICommandHandlerMetadata))
где ICommandExecutorMetadata
содержит тип команды, соответствующий командному обработчику
public interface ICommandHandlerMetadata
readonly property CommandType as Type
end interface
В исполнителе команд преобразование коллекции в словарь пар (Type, Lazy(of ICommandHandler, ICommandHandlerMetadata))
, где Type
- это тип команды, полученный из метаданных через Lazy.Metadata.CommandType
.
В методе CommandExecutor.Execute
ленивый инициированный обработчик команд получается из словаря по типу команды. Эта операция очень быстрая. Фактический CommandHandler
получается через Lazy.Value
.
Фактический CommandHandler
только один раз и точно, когда выполняется соответствующая команда.
ICommandExecutor
настоящее время? Что такоеICommandHandler<>
и как он используется. Показ реализацииICommandExecutor
поможет, потому что вы говорите, что проблема есть.