Di В Сложных Приложениях. Как Не Утонуть В Зависимости

Всем привет. При разработке приложений рекомендуется использовать внедрение зависимостей.

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

Это также упрощает тестирование, а код становится красивым, универсальным и заменяемым.

При разработке наших продуктов этот принцип использовался с самого начала: как в высоконагруженных DSP, так и в корпоративных Гибридный .

Мы писали модули, подключали интеграцию с различными системами, количество зависимостей росло и в какой-то момент стало сложно поддерживать саму конфигурацию приложения.

Плюс были добавлены неявные регистрации (например, в настройках Web Api был установлен собственный DependencyResolver для Web Api) и стали возникать трудности с чтобы вызов модулей конфигурации.

В итоге мы придумали подход к регистрации, настройке и инициализации модулей в сложном приложении.

Я расскажу вам о нем.



DI в сложных приложениях.
</p><p>
 Как не утонуть в зависимости

Во-первых, нужно уточнить, что для обслуживания различных задач (даже в рамках одного продукта) у нас есть несколько типов приложений: сервисы, консольные приложения, asp.net. Соответственно, система инициализации везде представляла свой зоопарк, единственное, что там был класс DependencyConfig с чертовой тучей зависимостей на любой вкус и цвет. Также каждое приложение имело свои дополнительные настройки.

Например настройка маршрутизации, конвертеров, фильтров авторизации в asp.net mvc, которые должны были вызываться после регистрации зависимостей и проверки правильности этой регистрации.

Соответственно, возникла задача:

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

В результате мы выделили 3 типа элементарных конфигураций: зависимости, инициализации и настройки, которые по сути являются комбинацией двух предыдущих.



Зависимости

Зависимость — это примитив для регистрации, ха-ха, зависимостей одного модуля.

В целом он реализует интерфейс IDependency:

  
  
  
  
  
  
  
  
   

public interface IDependency<TContainer> { void Register(TContainer container); }

где TContainer — IoC-контейнер (в дальнейшем в качестве примера контейнера используется SimpleInjector).

Соответственно, в методе 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

Вместе с данным постом часто просматривают: