В своей статье хочу рассмотреть пример неправильного, на мой взгляд, использования Dependency Injection принципа и попробовать отыскать мотивацию для других разработчиков команды (а может и кому еще сгодится) писать новый код лучше, а также по мере сталкивания в рамках рабочих активностей с чужим кодом, написанным неграмотным образом, делать рефакторинг.
Итак, суть проблемы. На проекте мы используем OData WebApi и все контроллеры наследуются от базового, используют метод GetService из базового класса который вытягивает зависимости через статический класс ApiControllerScopeContextMediator.
А в Global.asax конфигурируем подтягивание зависимостей для OData через StructureMap:
Во всех action у контроллеров повсеместно используется метод GetService, как, например, здесь:
Но почему? Ведь можно было бы просто использовать constructor injection:
Так что же все-таки: «Таити, Таити» (Constructor Injection) или «нас и здесь неплохо кормят» (GetService)?
Какие проблемы с этим кодом я вижу:
Какие аргументы «против» мне довелось услышать:
Пару лет назад прочитал книгу Марка Симана «Dependency Injection». Сижу и думаю, так что у меня с DI: любовь или брак по расчету?
Использованные материалы:
Mark Seeman «Dependency Injection»
Mark Seeman's blog
Microsoft MVC6 github open source project
SOLID wiki page
YAGNI wiki page
KISS wiki page
Итак, суть проблемы. На проекте мы используем OData WebApi и все контроллеры наследуются от базового, используют метод GetService из базового класса который вытягивает зависимости через статический класс ApiControllerScopeContextMediator.
public abstract class ODataControllerBase : ODataController
{
protected T GetService<T>()
{
return ApiControllerScopeContextMediator.GetService<T>(this);
}
}
internal static class ApiControllerScopeContextMediator
{
internal static T GetService<T>(ApiController controller)
{
return (T) controller.Configuration.DependencyResolver.GetService(typeof (T));
}
}
А в Global.asax конфигурируем подтягивание зависимостей для OData через StructureMap:
GlobalConfiguration.Configuration.DependencyResolver = new StructureMapDependencyResolver(container);
Во всех action у контроллеров повсеместно используется метод GetService, как, например, здесь:
public class DisconnectedAppsController : ODataControllerBase
{
public IHttpActionResult Get()
{
var query = GetService<IQuery<IQueryable<DisconnectedAppDomain>, DisconnectedAppFilter>>();
}
}
Но почему? Ведь можно было бы просто использовать constructor injection:
public DisconnectedAppsController(IQuery<IQueryable<DisconnectedAppDomain>, DisconnectedAppFilter> query){
_query = query;
}
Так что же все-таки: «Таити, Таити» (Constructor Injection) или «нас и здесь неплохо кормят» (GetService)?
Какие проблемы с этим кодом я вижу:
- Контроллеры нельзя покрыть unit тестами (без инициализации IoC контейнера)
- Согласно принципу KISS нам не нужна дополнительная сложность, а согласно YAGNI дополнительная завязка на System.Web.Http.Configuration.DependencyResolver, которая вылезет нам боком если мы захотим перейти с MVC5 на MVC6, у которого в корне изменится работы с Dependency Injection, а ApiController в бета версии уже лишился Configuration
- Взглянув на класс мы не видим явно все зависимости класса
- Использование GetService у DependencyResolver — это по сути использование реализации IoC при помощи Service Locator, что является анти-паттерном
- а Service Locator нарушает принципы SOLID
- Мы нарушаем фундаментальный принцип ООП инкапсуляцию
Какие аргументы «против» мне довелось услышать:
- В идеале контроллеры должны быть слишком тупы, чтобы их покрывать тестами, нам это никогда не понадобится, если и покрывать их, то интеграционными тестами
- Отказываться от текущей реализации основываясь на ссылках в гугле слишком идеалистично, это не принесет пользы
- Удобно для разработчика, меньше кода писать
- Изменение кода, который используется подобным образом во многих местах, внесет энтропию в проект
- Метод YAGNI в другой плоскости — а зачем нам менять что-то, если нет очевидной ежеминутной выгоды
- Constructor injection в контроллерах банально неудобен
Пару лет назад прочитал книгу Марка Симана «Dependency Injection». Сижу и думаю, так что у меня с DI: любовь или брак по расчету?
Использованные материалы:
Mark Seeman «Dependency Injection»
Mark Seeman's blog
Microsoft MVC6 github open source project
SOLID wiki page
YAGNI wiki page
KISS wiki page