Pull to refresh

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

Reading time3 min
Views7.9K
В своей статье хочу рассмотреть пример неправильного, на мой взгляд, использования 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
Tags:
Hubs:
-3
Comments78

Articles