Любовь или брак по расчету с Dependency Injection?

от автора

В своей статье хочу рассмотреть пример неправильного, на мой взгляд, использования Dependency Injection принципа и попробовать отыскать мотивацию для других разработчиков команды (а может и кому еще сгодится) писать новый код лучше, а также по мере сталкивания в рамках рабочих активностей с чужим кодом, написанным неграмотным образом, делать рефакторинг.

Итак, суть проблемы. На проекте мы используем 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

ссылка на оригинал статьи http://habrahabr.ru/post/271853/