Всем привет. При разработке приложений рекомендуется использовать внедрение зависимостей.
Такой подход позволяет сделать код слабосвязанным, а это, в свою очередь, обеспечивает простоту сопровождения.
Это также упрощает тестирование, а код становится красивым, универсальным и заменяемым.
При разработке наших продуктов этот принцип использовался с самого начала: как в высоконагруженных DSP, так и в корпоративных Гибридный .
Мы писали модули, подключали интеграцию с различными системами, количество зависимостей росло и в какой-то момент стало сложно поддерживать саму конфигурацию приложения.
Плюс были добавлены неявные регистрации (например, в настройках Web Api был установлен собственный DependencyResolver для Web Api) и стали возникать трудности с чтобы вызов модулей конфигурации.
В итоге мы придумали подход к регистрации, настройке и инициализации модулей в сложном приложении.
Я расскажу вам о нем.
Во-первых, нужно уточнить, что для обслуживания различных задач (даже в рамках одного продукта) у нас есть несколько типов приложений: сервисы, консольные приложения, asp.net. Соответственно, система инициализации везде представляла свой зоопарк, единственное, что там был класс DependencyConfig с чертовой тучей зависимостей на любой вкус и цвет. Также каждое приложение имело свои дополнительные настройки.
Например настройка маршрутизации, конвертеров, фильтров авторизации в asp.net mvc, которые должны были вызываться после регистрации зависимостей и проверки правильности этой регистрации.
Соответственно, возникла задача:
- унифицировать конфигурацию для разных типов приложений
- убрать необходимость указывать последовательность инициализации
- разбить регистрацию модулей на легковесные примитивы, изолированные друг от друга.
Зависимости
Зависимость — это примитив для регистрации, ха-ха, зависимостей одного модуля.В целом он реализует интерфейс IDependency:
где TContainer — IoC-контейнер (в дальнейшем в качестве примера контейнера используется SimpleInjector).public interface IDependency<TContainer> { void Register(TContainer container); }
Соответственно, в методе Register регистрируются сервисы одного логического модуля.
Другие примитивы IDependency также можно зарегистрировать, напрямую вызвав конструктор и метод Register. Пример: public class TradingDeskDependency : IDependency<Container>
{
public void Register(Container container)
{
container.Register(() => new SwiffyClient(new SwiffyOptions{ MillisecondsTimeout = 20000 }));
new DspIntegrationDependency().
Register(container);
}
}
Инициализация (IInit)
Инициализации включают в себя код, который должен быть выполнен после регистрации и проверки зависимостей, но до запуска основной логики приложения.Это может быть настройка asp.net mvc и веб-API или что-то подобное.
В общем, класс инициализации реализует интерфейс IInit: public interface IInit
{
void Init(IDependencyResolver resolver);
}
где IDependencyResolver нужен, если нужно получить какой-то сервис из зависимостей, или получить методы получения самих зависимостей, как в примере: public class AspNetMvcInit: IInit
{
public void Init(IDependencyResolver resolver)
{
System.Web.Mvc.DependencyResolver.SetResolver(resolver.GetService, resolver.GetServices);
new RouteInit().
Init(resolver);
}
}
Как и в случае с зависимостями, вы можете использовать вложенные примитивы инициализации.
Настройки
Настройки нужны, если логический модуль требует как регистрации зависимостей, так и последующего вызова инициализации.
Они описываются наиболее просто: public interface ISettings<TContainer> : IDependency<TContainer>, IInit
{
}
Соответственно, именно настройки обеспечивают полный функционал по настройке логического модуля: как регистрацию зависимостей, так и дополнительные настройки.
Общий дизайн
Итак, у нас есть примитивы, на которые можно разделить конфигурацию, осталось только настроить управление ими.
Для этого нам поможет класс Application, реализующий интерфейс IApplication: public interface IApplication<TContainer>
{
IApplication<TContainer> SetDependency<T>(T dependency) where T : IDependency<TContainer>;
IApplication<TContainer> RemoveDependency<T>() where T : IDependency<TContainer>;
IApplication<TContainer> SetInit<T>(T init) where T : IInit;
IApplication<TContainer> RemoveInit<T>() where T : IInit;
IApplication<TContainer> SetSettings<T>(T settings) where T : ISettings<TContainer>;
IApplication<TContainer> RemoveSettings<T>() where T : ISettings<TContainer>;
IAppConfig Build();
}
Как видно из кода, IApplication позволяет добавлять все типы настроек (а также удалять их).
А метод Build вызывает код, который собирает все эти настройки: сначала регистрируются зависимости (+ при необходимости производится проверка, все ли можно прописать), затем код из модулей IInit (и методов Init в IНастройки).
Результатом является объект IAppConfig: public interface IAppConfig
{
IDependencyResolver DependencyResolver { get; }
IAppLogger Logger { get; }
}
где DependencyResolver позволяет получать сервисы, а Logger сами понимаете почему.
Итоговый код настройки приложения будет простым и прозрачным (хотя в общем случае с некоторыми усложнениями для универсальности): var container = new Container()
var appOptions = new AppOptions
{
DependencyContainer = container,
GetServiceFunc = container.GetInstance,
GetAllServicesFunc = container.GetAllInstances,
VerifyAction = c => c.Verify(),
Logger = new CustomLogger()
};
var appConfig = new Application(appOptions).
SetDependency(new TradingDeskDependency()) .
SetInit(new AspNetMvcInit()) .
Build();
Единственный класс, который необходимо будет определить явно, — это CustomLogger. Если мы хотим отслеживать ситуации, когда регистрация зависимостей и инициализаций завершается с ошибкой, то нам следует ее установить.
Регистратор описывается простым интерфейсом: public interface IAppLogger
{
void Error(Exception e);
}
и написать реализацию не сложно.
В результате этот подход можно использовать для любого типа приложений, нам не нужно думать о порядке настройки и в управлении зависимостями мы перешли на уровень логических модулей.
Таким способом можно добавить кучу настроек, зависимостей, инициализаций, сохранив при этом трезвость мысли и силу духа.
Я написал немного упрощенную (но вполне рабочую и легко расширяемую) версию библиотеки (Jdart.CoreApp), которую вы можете изучить или просто использовать: 1) GitHub 2) Нугет .
Также имеются адаптеры для 1) ПростойИнжектор 2) Автофак 3) Нинжект 4) Единство Спасибо всем.
Теги: #архитектура #C++ #Идеальный код #простота в коде #программирование #Идеальный код #.
NET
-
Devuan, Форк Debian Без Systemd
19 Oct, 24 -
Тагир И Егор: Интервью С Тагиром Валеевым
19 Oct, 24 -
Сохранение Реестра Своими Руками
19 Oct, 24 -
Avaya Ip Office Для Чайников
19 Oct, 24 -
Пермь — Новое Написание Названия Города
19 Oct, 24