В докладе рассказывается об опыте применения «инверсия управления» (Inversion of Control) при разработке новой версии KES. Этот подход заключается в том, что более высокоуровневый код не зависит напрямую от конкретной реализации нижележащего слоя. Вместо этого он зависит от абстрактного протокола (интерфейса), конкретный же компонент подставляется конфигурационным кодом-клиентом. Эта практика позволяет понизить loose coupling программных модулей и применяется практически в любых крупных проектах.
При разработке новой версии KES было принято решение изменить подход к реализации инверсии управления. Было решено отказаться от централизованного обобщенного реестра доступных компонентов (шаблон (паттерн) Service Locator) в пользу явной передачи зависимостей конфигуратором (ручная инъекция зависимостей (manual Dependency Injection)). При это возникли проблемы с использованием готовых библиотек Dependency Injection Frameworks. Применение подобных библиотек стало стандартом в мире разработки Java/C# за последние 10-15 лет, но в мире C++ они пока не получили подобного распространения. В докладе делается обзор и сравнение актуальных DI-Framework’ов на C++, анализируется их применимость к практическим задачам ЛК. Анализируется, что могут привнести стандарты C++11/14 для упрощения решения таких задач.
2. Из опыта разработки новой версии Kaspersky Endpoint
Security for Windows:
Использование Service Locator. Подводные камни;
Использование Dependency Injection. Подводные камни;
Обзор современных C++ DI-фреймворков. Перспективы;
Содержание
2
3. Java/C# разработчик и Dependency Injection
C++ разработчик и Dependency Injection
C++ разработчик и Dependency Injection на C++11/14
3
4. Service Locator (SL) и Dependency Injection (DI) – способы реализации
инверсии управления (Inversion of Control, IoC)
class VirusDetector
{
shared_ptr<VirusBases> m_virusBases;
public:
VirusDetector()
{
m_virusBases = make_shared<VirusBases>();
}
...
};
Прямое управление
VirusDetectorC VirusBasesC
4
10. Из опыта разработки новой версии Kaspersky Endpoint
Security for Windows:
Использование Service Locator. Подводные камни;
Использование Dependency Injection. Подводные камни;
Обзор современных C++ DI-фреймворков. Перспективы;
Содержание
10
15. AVP: unit-тестирование
Service Locator Component Dependency Injection Component
VirusDetectorC
IVirusBasesI
VirusBasesMockC
IServiceLocatorI
ServiceLocatorMockC
VirusDetectorC IVirusBasesI
VirusBasesMockC
См. также «получение списка зависимостей»15
16. DI в разных типах приложений
Service Locator Component Dependency Injection Component
SL
Application
DI
Application
Application
with SL of
another type
Non-IoC
Application
SL
Application
DI
Application
Application
with SL of
another type
Non-IoC
Application
Adapter
SL
component
DI
component
SL
component
SL
component
SL
component
Adapter Adapter
DI
component
DI
component
DI
component
16
18. Из опыта разработки новой версии Kaspersky Endpoint
Security for Windows:
Использование Service Locator. Подводные камни;
Использование Dependency Injection. Подводные
камни;
Обзор современных C++ DI-фреймворков. Перспективы;
Содержание
18
19. AVPSUS: Подводные камни DI
1. Объекты создаются всегда, вне зависимости от частоты использования
19
class VirusDetector {
const shared_ptr<IVirusBases> m_virusBases;
const shared_ptr<IRecoveryManager> m_recoveryManager;
public:
VirusDetector(shared_ptr<IVirusBases> virusBases,
shared_ptr<IRecoveryManager> recoveryManager)
: m_virusBases(move(virusBases))
, m_recoveryManager(move(recoveryManager)) {}
void DetectViruses() {
try
{/* Detect */}
catch (const std::exception&)
{
m_recoveryManager->Recover();
}
}
};
21. 3. Рост сложности конфигурации
void Configure()
{
auto vbFactory = CreateVirusBasesFactory();
auto vbConfig = LoadVirusBasesConfiguration();
auto virusBases = vbFactory->CreateVirusBases(vbConfig);
auto recoveryManager = TryCreateRecoveryManager();
if (!recoveryManager)
LogWarningRecoveryManagerNotAvailable();
auto amDetector = make_shared<AntimalwareDetector>(virusBases);
auto arDetector = make_shared<AntiRootkitDetector>(virusBases, amDetector);
auto webAnalyzer = make_shared<WebAnalyzer>(amDetector, recoveryManager);
auto mailAnalyzer = make_shared<MailAnalyzer>(amDetector, recoveryManager, webAnalyzer);
auto trafficProcessor = make_unique<TrafficProcessor>(arDetector, webAnalyzer, mailAnalyzer);
// ...
}
21
AVPSUS: Подводные камни DI
22. Из опыта разработки новой версии Kaspersky Endpoint
Security for Windows:
Использование Service Locator. Подводные камни;
Использование Dependency Injection. Подводные камни;
Обзор современных C++ DI-фреймворков.
Перспективы;
Содержание
22
23. DI фреймворк: перспектива развития AVPSUS
DI фреймворк – средство конструирования объектов на основе
заданной конфигурации зависимостей.
Мотивация:
• Отделение конфигурации от логики;
• Декларативность описания конфигурации;
23
24. Технические требования:
• Простота конфигурирования компонентов;
• Явность списка зависимостей компонент;
• Обнаружение ошибок конфигурации на этапе компиляции;
• Отсутствие необходимости изменять код компонентов;
• Минимальные зависимости на этапах сборки и выполнения;
Общие требования:
• Лицензия допускает коммерческое использование;
• Прохождение апробации в крупных проектах;
• Активное развитие и поддержка проекта;
DI фреймворк: требования AVPSUS
24
33. Poco
Capsule
Wallaroo
Google
Fruit
Boost.DI
manual
DI
Простота конфигурирования
компонентов
Да Да Да Да Нет
Явность списка зависимостей
компонент
Нет Нет Да Нет Да
Обнаружение ошибок конфигурации
на этапе компиляции
Нет Нет Да Да Да
Отсутствие необходимости изменять
код компонентов
Да Нет Да Да Да
Минимальные зависимости на
этапах сборки и выполнения
Нет Да Да Да Да
33
DI фреймворки: сравнение
35. DI в современном C++: выводы
• Dependency Injection предпочтительнее Service Locator для
связывания объектов;
• Для C++-проекта с инверсией зависимости на текущий момент
предпочтительнее использовать manual DI либо DI-фреймворк
собственной разработки для связывания объектов.
35
(Сергей) Представление
(Сергей) Незримо команда КЕС
(Сергей) Рад приветствовать
(Сергей) Опыт из разработки KES (Enterprise)
(Сергей) КЕС – большой проект – компоненты – инверсия управления – как у нас – SL&DI – плюс-минус-ПК
(Сергей) Развитие как DI Framework – обзор – применимость по нашему опыту – перспективы C++11/14
(Сергей) джава в зале
(Сергей) большинство в зале
(Сергей) C++11/14 – заинтересовать, посмотреть под новым углом
(Сергей) Хватит воды, код, Витя
Поясним понятия DI, SL и IoC на примере.
При традиционном подходе компонент создает конкретную реализацию
Аналогия
Это проблема потому что добавление функцмональности требует изменения отлаженного, оттестированного кода
Вместо зависимости от конкретной реализации – от интерфейса в конструкторе. Таким образом VirusDetector начал управлять, а не зависеть
Вставляем сервмсы от которых зависит и только их
Альтернатива – SL
Компоненты как нужные, так и не нужные
Это удобно (на все случаи жизни), компонента по-прежнему зависит от интерфейса, а не от реализации
Возвращаясь к аналогии
Описанные подходы мы использовали при разработке новой версии Endpoint Securit
Задача – сделать внешний модуль обновления для антивируса (DI).
При этом использовали существующие компонент (SL)
SL представлен в продукте интерфейсом c методом
Который по id интерфейса -> имя модуля и id класса-реализации
В модуле вызывает статический метод для создания фабрики, с помощью которой создается требуемая реализация
Посмотрим с какими проблемами использования SL побудили использовать DI
При переносе существующей SL-компоненты (т.е. Комопоненты, использующей SL в реализации) в новое окружение требуется получить список зависимых сервисов
SL – поиск и отсмотр всех вхождений
DI – список получается явно из параметров конструктора
В случае SL сложно контролировать список Например, если в процессе эволюции повторно используемого компонента появилась новая зависимость, это будет скрыто от пользователя SL-компоненты и очевидно пользователю DI-компоненты
Еще одна проблема SL – усложнение юнит-тестирования. Нужен дополнительный mock-объект.
В то же время наш опыт показал, что эта сложность переоценена. Гораздо больше юнит-тестирование усложняется ранее озвученной сложностью получения списка зависимостей;
Еще одной проблемой SL является его навязчивость, т.е. при использовании SL-компоненты необходимо использовать и SL, а в случае его отсутствия реализовывать адаптер.
DI компоненту же можно использовать без дополнительных сложностей.
Надо отметить, что в нашей ситуации выбора между SL & DI при разработке нового приложения данный минус SL являлся аргументом за то чтобы продолжить его использование.
Таким образом, мы пришли к выводу что для нового приложения AVPSUS предпочтительнее использовать DI.
При этом нам потребовалось для каждой SL-компоненты создавать индивидуальный адаптер сервис-локатора и наполнять его теми и только теми сервисами, от которых зависит данный конкретный комопнент.
(Сергей) Рассмотрели шишки AVP. Перенесемся во времени, рассмотрим шишки AVPSUS. И тут не все гладко.
(Сергей) Один из минусов – отсутствие бесплатного ленивого создания;
(Сергей) Можно обойти с помощью дополнительных фабрик, но это дополнительная работа;
(Сергей) По нашему опыту не так часто важно, минус не существеннен;
(Сергей) Другой проблемой считается разрастание
(Сергей) Было на AVPSUS, думали беда
(Сергей) Подумали лучше, поняли что проблема в дизайне
(Сергей) 1. Много зависимостей
(Сергей) 2. Ненужное пробрасывание зависимостей, частичная реализация DI
(Сергей) Сделали рефакторинг. Миф разрушен
(Сергей) Наконец реальная проблема
(Сергей) Нельзя вечно зависеть от абстракции, есть конкретика – в конфигурации
(Сергей) Большому проекту большая конфигурация. 1000 строк
(Сергей) Разложенный в линию граф. Трудно вручную вносить изменения
(Сергей) DI не панацея. Существует класс решений. Что такое ДИ фреймворк?
(Сергей) DI фреймворки предоставляют возможность декларативно описывать конфигурацию, отделив ее от повторно используемой логики. В идеале это позволит создавать различные продукты из готовых компонент, изменяя лишь конфигурацию
(Сергей) Сформулируем требования
(Сергей) В первую очередь решать задачу
(Сергей) Конечно, при этом не хотим регресс (список, раннее обнаружение ошибок)
(Сергей) Большая кодовая база – не хотим менять код
(Сергей) Желательно упростить внедрение, минимум зависимостей
(Сергей) Помимо этого требования общего вида
(Сергей) Конечно, лицензия
(Сергей) Проверенность временем и индустрией. Активная Поддержка авторами
(Сергей) Надо отметить что требования специфичны AVPSUS. По сути нам нужно решение для DI на уровне объектов. На более высоком уровне (пример AVP) требования иные. Возможны даже комбинации решений.
(Сергей) Давайте рассмотрим
/*
Сформулируем требования, которым должен удовлетворять DI фрейфорк, чтобы мы могли его применить в AVPSUS.
В первую очередь он должен упрощать конфигурацию. При этом нам важно не потерять преимуществ DI над Sl, а именно явного списка зависимостей компонентов и раннего обнаружения ошибок. С учетом большой кодовой базы мы не можем себе позволить использовать фреймворк, требующий изменений в коде компонент. Желательно минимизировать зависимости фреймворка для упрощения процесса внедрения.
Помимо технических есть ряд требований общего характера: лицензия должна позволять использовать фреймформ
*/
Зависимости описываются конфигурационным xml-файлом;
Поддержка вызовов методов после инициализации
Двухэтапный цикл сборки:
– Усложняет процесс работы;
+ Удобен для компонентной модели разработки;
+ Изменения кода приложения не требуют перекомпиляции конфигурации.
Применим скорее на другом уровне: для управления компонентами в сложных многомодульных приложений
Макрос WALLAROO_REGISTER для генерации фабричных методов создания обьектов и их генерации в реестре wallaroo;
Явное создание и связывание обьектов: изменяется глобальный реестр wallaroo;
Возможность изменять граф зависимостей во время выполнения приложения;
Ограничение на количество аргументов в конструкторе.
Дублирование сигнатуры конструктора или макрос INJECT для определения сигнатуры конструктора;
Разбор сигнатуры для формирования списка зависимостей;
Объекты создаются с помощью графа зависимостей классов и списка соответствия интерфейсов и реализаций
Для разбора сигнатуры конструкторов используется [C++ 11] type traits;
В результате разбора формируется граф зависимостей классов;
Обьекты создаются на основе списка соответствия интерфейсов и реализаций из класса injector и графа зависимостей классов;
Собрав воедино результаты оценок фрейморков согласно нашим требованиям, получили следующую таблицу.
Два лидера: Fruit и BoostDI. Оба проекта активно ращвиваются, но еще сыроваты. В частности Fruit недостаточно настраиваем (не поддерживает кастомные аллокаторы, умные указатели и др.), а Boost.DI один инжектор на все компоненты, что возвращает фреймворк к минусам SL.
(Сергей) Подведем итоги
(Сергей) Мы рассмотрели опыт использования DI & SL на реальном проекте. Сделали вывод про предпочтительность DI (хотя и он не идеален);
(Сергей) Рассмотрели DI-фреймворки.
(Сергей) В отличие от Java/C# на текущий момент на C++ нет признанныз фреймворков такого типа. Периодически появляются и исчезают;
(Сергей) C++11/14 открывает перспективы развития метапрограммирования, можно ожидать дальнейшего развития Compile-Time DIFw
(Сергей) Для своего проекта сделали вывод о manual DI пока (но надеемся на лучшее).
(Сергей) Надо понимать, что для других проектов и других целей критерии могут быть отличные. Например ABI. В частности, когда речь идет не о связывании отдельных объектов а крупноблочных компонентов мы планируем продолжать использовать Service Locator.
(Сергей) Спасибо, вопросы