Страницы

Поиск по вопросам

среда, 5 июня 2019 г.

Внедрение списка зависимостей с помощью контейнера Castle.Windsor

В качестве IoC-контейнера используется Castle.Windsor.
Корневой элемент приложения - планировщик Scheduler, который выполняет в определенной последовательности различные задания. Задания реализуют интерфейс IWorker, поэтому я решил внедрить(inject) List в Scheduler(список заданий). Впоследствии, планируется много различных типов заданий, сейчас их два: Cleaner и Writer. Cleaner нужен в одном экземпляре, используем Синглтон. Необходимо правильно внедрить N экземпляров Writer, где N - количество XML файлов в поддиректории приложения. Каждый из XML-файлов надо десериализовать и передать в качестве входного параметра каждому Writerу. Т.е. надо внедрить в Schedulerимплементацию для каждого XML. Как это правильно реализовать? Вообще какой паттерн используется в таком случае? Фабрика?
Конфигурация контейнера Castle.Windsor:
var container = new WindsorContainer(); // Для внедрения List container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true)); // Регистрация Scheduler container.Register(CastleRegistration.Component.For().ImplementedBy); // Регистрация Workers... container.Register(CastleRegistration.Component.For().ImplementedBy()); container.Register(CastleRegistration.Component.For().ImplementedBy()); // ...количество зависит от количества файлов + каждому надо передать десериализованный XML в качестве параметра container.Register(CastleRegistration.Component.For().ImplementedBy()); // Забираем Scheduler var sched = container.Resolve();
Scheduler
public class Scheduler : IScheduler { public IList Workers { get; set; }
public Scheduler(IList workers) { Workers = workers; } }
Writer
public class Writer : IWorker { public string Source { get; set; }
public Writer() { }
public Writer(string source) { Source = source; } }
Cleaner
public class Cleaner : IWorker { public Cleaner() { } }
UPDATE: Дополнительный вопрос:
Содержимое Xml-файлов переехали в одну из таблиц БД. В Scheduler (корневой компонент) нужно внедрить по Writerу с параметром на каждую запись в таблице. Из полезного совета в ответе(Andrew Prigorshnev), следует, что обращаться к БД будет правильным после настройки контейнера и получения из него корневого компонента.
С точки зрения архитектуры, правильным ли будет внедрить в Scheduler компоненты: WriterFactoryи Repository(слой доступа к БД), после чего получить экземпляр Scheduler, затем в нем с помощью Repository получить список параметров, а потом сделать на каждый параметр по Writerу с помощью WriterFactory?


Ответ

Насколько я понимаю в наиболее общем виде вопрос состоит в следующем: как правильно внедрить список зависимостей с помощью контейнера Castle Windsor?
Ответ следующий. Сам подход с внедрением списка через конструктор вполне корректный и Castle Windsor умеет работать с такими сценариями. Чтобы внедрить список зависимостей надо сделать следующее:
Зарегистрировать в контейнере резолвер списков. Вы это уже сделали в строчке container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true)); Зарегистрировать в контейнере несколько компонентов, которые должны быть внедрены в виде списка. При этом если вы внедряете список типа IList контейнер добавит в список все компоненты типа IWorker, которые вы зарегистрируете. Это вы сделали частично. В вашем примере не учтено, что когда вы регистрируете несколько одинаковых компонентов они должны быть именованными (делается с помощью метода .Named()), и по всей видимости вы не знаете как внедрить строку в конструктор компонента - это делается с помощью метода .DependsOn()
Таким образом правильная реализация вашей задачи должна выглядеть вот так:
var container = new WindsorContainer();
// Для внедрения List container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true));
// Регистрация Scheduler container.Register(Component.For().ImplementedBy());
// Writers container.Register(Component .For() .ImplementedBy() .DependsOn(Dependency.OnValue("source", "files/first.xml")) .Named("firstWriter"));
container.Register(Component .For() .ImplementedBy() .DependsOn(Dependency.OnValue("source", "files/second.xml")) .Named("secondWriter"));
container.Register(Component .For() .DependsOn(Dependency.OnValue("source", "files/third.xml")) .ImplementedBy() .Named("thirdWriter"));
// Cleaner container.Register(Component.For().ImplementedBy());
// Забираем Scheduler var scheduler = container.Resolve();
И еще несколько замечаний, касающихся конкретно вашей реализации:
Вы хотите передавать в воркеры xml, но это плохая идея. Тогда вам придется прочитать его из файла прямо в корне компоновки, в том месте где вы настраиваете контейнер. Но чтение из файла это отдельная задача, никак не связанная с разрешением зависимостей, поэтому делать ее в корне компоновки не стоит. Пусть этим занимается один из ваших компонентов. В связи с этим, в примере кода выше вы можете увидеть что во Writer'ы передается путь к файлу, а не его содержимое. Если файлы, которые вы обрабатывате меняются редко и их имена и количество известны на этапе написания кода, пример приведенный выше может вас полностью устроить. Но если это не так, надо сделать еще немного дополнительной работы, и как вы правильно предположили, надо использовать фабрики. Возможное решение здесь следующее. Надо добавить новую абстракцию - поставщик воркеров, который будет представлять собой фабрику, создающую воркеры:
public interface IWorkersProvider { IEnumerable GetWorkers(); }
Поставщик Cleaner'ов будет просто возвращать один Cleaner. А поставщик Writer'ов может получать как аргумент конструктора путь к папке, в которой надо искать файлы, и затем в методе GetWorkersсоздавать по одному воркеру на каждый найденный файл:
public class WritersProvider : IWorkersProvider { public WritersProvider(string folderPath) { }
public IEnumerable GetWorkers() { // здесь создаем по одному воркеру на каждый найденный файл } }
При такой архитектуре ваш Sheduler должен получать вместо списка воркеров, список поставщиков воркеров:
public class Scheduler : IScheduler { public Scheduler(IList providers) { // здесь надо опросить все поставщики, чтобы сформировать // список воркеров } }
Регистрация такой конфигурации в Castle Windsor делается с помощью тех же методов, что и регистрация в первом примере, поэтому не буду здесь ее описывать.
UPDATE: Ответ на дополнительный вопрос
Лучше сделать так:
В Scheduler внедрить список поставщиков воркеров (IEnumerable), как описано выше Внедрить Repository в конструктор WritersProvider и уже во WritersProviderопрашивать базу данных
Идея здесь вот в чем. Каждый класс должен выполнять одну единственную задачу. Задача Scheduler-а взять готовый список воркеров и запустить их. Задача поставщиков воркеров - создать воркеры на основе какого-то правила (из xml-файлов, таблиц бд и т.д.). Разделив обязанности таким образом, вы получите код, который легко модифицировать. Если вы захотите поменять что-то в логике запуска воркеров, вы прийдете и поменяете только один класс - Scheduler, при этом вы будете уверены, что вы никак не сломали логику создания воркеров, потому что эту работу выполняют совсем другие классы.
Потенциально у вас может быть множество поставщиков воркеров, работающих по разным правилам. Один поставщик может всегда возвращать один и тот же жестко закодированный воркер, второй создавать по воркеру на каждый xml-файл в какой-нибудь директории, третий создавать воркеры на основе таблицы в базе данных, четвертый получать список воркеров, которые надо создать, у удаленного сервиса и т.д. Более того, в процессе развития проекта у поставщика воркеров может меняться правило, по которому он создает воркеры (у вас это уже произошло - вместо xml-файлов у вас теперь таблица в бд.)
Если поставщику воркеров для выполнения его работы нужен доступ к бд, внедряйте Repository прямо в поставщик. Если потом это правило изменится, прийдете и поменяете только конкретный поставщик, не трогая другие поставщики и не трогая Scheduler
Вариант с фабрикой похуже, но тоже годится. Но если будете внедрять фабрику воркеров то Repository внедряйте уже в нее, а не в Scheduler
Фабрика хуже, потому что, когда количество воркеров вырастет, вы все равно будете вынуждены разнести создание воркеров разных типов по отдельным классам, иначе фабрика будет слишком большой и запутанной. В итоге вы все равно создадите несколько фабрик (что полность аналогично моему варианту со списком поставщиков воркеров, отличие только в названии, фабрика и поставщик в данном случае одно и то же).

Комментариев нет:

Отправить комментарий