Видео. Управление зависимостями в коде

7 апреля 2010 г.

Я рассмотрел, как эволюционировал подход к управлению зависимостями в коде. Какие проблемы возникали на каждом этапе и как эти проблемы решались. Возможно на каком-то этапе вы узнаете свой проект и поймете куда двигаться дальше.

Основные темы:

Перед этим видео желательно посмотреть пример разработки приложения с помощью TDD.


Исходники проекта

35 комментариев:

  1. Спасибо большое. Очень рад, что подписался на Ваш блог :)

    Хочу поинтересоваться, как и где вы собираете сложные ViewModel для страниц и контролов в asp.net mvc. Я имею в виду ситуации, когда для сборки ViewModel оптимальнее использовать join (SQL/Linq), вместо нескольких запросов к репозитарию из слоя бизнес логики.

    Ещё раз спасибо за такую наглядную статью.

    ОтветитьУдалить
  2. @SpeCT
    Привет!

    Тут нет универсального решения. Данные в модель собираются в контроллере, сами данные формируются в сервисе.

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

    ОтветитьУдалить
  3. Несколько несущественных замечаний.
    1. Где-то на 12-ой минуте слово "хрупкость" употреблено не по делу. Правильнее было сказать "жесткость".

    2. Русское произношение некоторых слов (нулл вместо налл, продУкт, вместо прОдакт, репозИтори, вместо репОзитори и т.п.) несколько корежит слух. :)

    3. 24 минута. Сервис-локатор конечно же тоже можно доточить до состояния, в котором он будет управлять временем жизни создаваемых сервисов так, как вам надо. Фактически IoC-контейнер — это тоже сервис-локатор, только не статический.

    ОтветитьУдалить
  4. не могли бы выложить исходники?

    ОтветитьУдалить
  5. @xoposhiy
    дело не в произношении, а в сути.

    ОтветитьУдалить
  6. Хорошо что хоть еще кто-то в рунете что-то делает по дот-нету, а то я уж думал что смерть пришла :)

    ОтветитьУдалить
  7. Спасибо, доклад очень понравился! Всегда удовольствием читаю (и слушаю) твой блог, Александр

    ОтветитьУдалить
  8. Вопрос - зачем в примере нужен слой сервисов? Почему бы контроллеру не обращаться сразу к репозиторию?

    ОтветитьУдалить
  9. @Михаил Петров
    Вы не поверите, этот вопрос стал самым популярным =)

    Дело репозитория - выборка данных. Дело сервиса - подготовка данных под конкретную бизнес-задачу.

    Функция GetLastProduct может использоваться не только ProductService и не только в функции FindLastCreatedProduct. Таким образом мы разделяем ответственность и избавляемся от дублирования.

    ОтветитьУдалить
  10. т.е. репозитарий должен обладать более "низкоуровневым" поведением, большим набором простых методов, которые могут использоваться в произвольном порядке и в разных бизнес-сценариях?

    ОтветитьУдалить
  11. Добрый день.
    Можно получить исходники примеров на различных этапах демонстрации?

    PS: Какая версия ReSharper'a установлена в данном видео-примере?

    ОтветитьУдалить
  12. Александр, извиняюсь за оффтоп, возникло пару вопросов по ходу просмотра и в процессе ознакомления с вашим блогом.
    Тут конечно возможны разные подходы, меня интересует именно тот который предпочитаете вы.

    Репозиторий отвечает за выборку данных. Только за выборку.(?) За persistence объектов бизнес логики отвечает другой компонент/подход/паттерн. Вопрос - какой и как?

    Второй вопрос. Репозиторий в конкретном этом примере имеет паблик метод getLastProduct, то есть это по сути метод реализующий бизнес требование, сервис нуждается в последнем продукте и мы в репозитории реализуем метод который нам это делает.
    Так как эти бизнес требования могут постоянно меняться плюс, например, этот репозиторий может использоваться из других сервисов - может потребоваться расширифть функциональность репозитория. Например может потребоваться там getLast10ProductsBeforeSomeSpecificDate.
    Каковы были бы ваши действия? Расширение / декорирование репозитория? Модификация интерфейса?..
    Введете работу с какими-то объектами запросов или дополнительную параметризацию методов репозитория прямо из сервисного слоя?

    Спасибо!

    ОтветитьУдалить
  13. @s-stude
    Исходники выложил, но только заключительные.

    ReSharper 5 beta

    ОтветитьУдалить
  14. @lcf

    > Репозиторий отвечает за выборку данных. Только за выборку.(?) За persistence объектов бизнес логики отвечает другой компонент/подход/паттерн. Вопрос - какой и как?

    Он отвечает за выборку и CRUD. Мы используем NHibernate, поэтому наши репозитории являются обертками над этой ORM.

    > Каковы были бы ваши действия? Расширение / декорирование репозитория? Модификация интерфейса?
    Здесь нельзя сказать наверняка, потому что нет универсальных решений, все зависит от специфики проекта.

    В каком-то случае будет удобно переделать функцию на GetLastProducts(int productCount) и все.

    Но, если библиотека с этим репозиторием уже используется в куче других проектов, то поменять API кнопочкой F2 не получится, иначе придется менять вызов этой функции во всех связанных проектах. В этом случае придется скорее всего расширять интерфейс.

    ОтветитьУдалить
  15. В своем видео ты говоришь о том, что мы избавились от зависимости например между слоем логики и слоем доступа к данным. А как же сам класс Product? От нем же знают все слои приложения? То есть если он входит в слой логики, то получается все слои зависят от слоя логики. Как с этим быть? :)

    ОтветитьУдалить
  16. @Михаил Петров
    Класс Product входит в домен приложения, а о нем по-умолчанию знает весь проект. Это нормально.

    ОтветитьУдалить
  17. Спасибо за оперативный ответ! В принципе, так и думал, но на всякий случай решил уточнить :)

    ОтветитьУдалить
  18. Активная инъекция зависимостей очень гармонично вписывается в инфраструктуру ASP.NET MVC, в которой есть такое единое место -- фабрика контроллеров -- в котором инъекция и производится. А как быть в классическом ASP.NET?

    ОтветитьУдалить
  19. @sombre hombre
    Там тоже все нормально. Сейчас как раз готовлю статью об этом. Если коротко, то надо использовать шаблон шаблон MVP, более скоро будет =)

    ОтветитьУдалить
  20. Заметил, что в примере вы регистрируете контроллеры (IController) в WindsorContainer`е без указания LifeStyle, т.е. по умолчанию их поведение будет определено как Singleton. Я правильно понимаю, что надежнее указать LifeStyle.Transient?

    ОтветитьУдалить
  21. @dRn
    Спасибо, что обратили внимание. Код инициализации IoC-контейнера был скорее для демонстрации возможностей.

    ОтветитьУдалить
  22. Привет

    Не совсем понятно, зачем интерфейс IDataContext выносить наружу DomainModel.MyOrmImplementation.

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

    НО: взаимоотношения конкретной реализации репозитория (ProductRepository) и конкретной реализации контекста (MyOrmDataContext) это ведь их интимные дела. Зачем притягивать за уши к датаконтексту общий интерфейс, с методами Save, Delete, Get итд, если всё равно "что и как" получает репозиторий - только его сугубо личное дело. Нас (сервисный слой) интересует результат работы, а не конкретный процесс.

    Как я считаю было бы правильно (естественно - субъективно): перенести IDataContext в сборку DomainModel.MyOrmImplementation.

    ОтветитьУдалить
  23. @zerkms
    Привет!

    IDataContext находится в сборке Infrastructure, которую используют другие проекты.

    Сборка DomainModel.MyOrmImplementation очень специфическая для проекта и в любой момент может быть заменена другой, либо часть реализаций репозиториев, которые использовали ORM1 могут начать использовать ORM2. Тогда опять же понадобится вынести IDataContext выше.

    Но, это конечно не правило, которое нельзя нарушать. Если аргументы, которые я описал выше не про твой проект, то перености IDataContext в DomainModel.MyOrmImplementation. Со временем рефактиринг покажет, как будет лучше ;)

    ОтветитьУдалить
  24. Александр, извините за оффтоп :)

    Какой программой записывает видеокасты?

    ОтветитьУдалить
  25. Александр, спасибо! Очень подробно все - лучшее объяснение IoC, которое видел.

    Подскажите пожалуйста, что можно почитать, чтобы узнать, как правильно создавать структуру проекта: то есть почему у вас те интерфейсы находятся там-то, другие - там-то итд. Спасибо.

    ОтветитьУдалить
  26. @Maxim
    Я думаю, что такого описания нет.

    Конкретно про интерфейсы http://martinfowler.com/eaaCatalog/separatedInterface.html

    ОтветитьУдалить
  27. Александр хочу поблагодарить Вас за проделанную работу. Ваши статьи очень помогают встать на путь истинный :)

    У меня возникли некоторые сложности с реализаций подхода описанного в этой статье. Вернее сам подход мне понятен. Но вот такой момент, у Вас получается во всех интерфейсах есть явная зависимость от сущности. В частном случае от Product. А как быть если сущность моя есть сущность из Linq2Sql, а так как основная идея которую я хотел реализовать это не зависеть от ORM, то просто так это сделать не получается. И хотел бы спросить у Вас совета пока не нагородил какой нибудь велосипед. Читал у Вас статью про (TDD и LLBLGen Pro) но пока не удается удачно соединить ее и эту :)

    ОтветитьУдалить
  28. @Warlock
    Рад, что помог.

    На самом деле это проблема всех ORM, которые занимаются генерацией кода. Я бы предложил вам перейти на NHibernate. Можете сразу воспользоваться готовой инфраструктурой из статьи Совершенный код №2. Пример реализации Unit Of Work с NHibernate.

    Иначе, доменные сущности придется наследовать от интерфейсов. Т.е. у Product будет IProduct. Это значительно увеличит кол-во кода и затруднит поддержку проекта.

    ОтветитьУдалить
  29. Добрый вечер.
    У меня вопрос по инверсии зависимостей и инжекции параметров в конструктор при использовании Ninject.

    Например у меня есть IRepositoiry с двумя обязательными параметрами в конструкторе. Его я инжектирую через конструктор в сервис. Сам сервис ижектирую в конструктор Presenter'a.

    Когда я описываю модуль для Ninject'a я могу binde'ить интерфейсы к реализациям. Как мне забайндить интерфейс репозитория к реализации, если у меня есть два параметра, которые будут определены в runtime? (параметры, например, указывают к каким БД подключаться. Это пользователь выберет из дроп дауна...)

    ОтветитьУдалить
  30. @s-stude

    Это типичная проблема. Скорее всего вам придется создавать этот репозиторий не чего активную инжекцию, а через MyIoC.Resolve(connection)

    IoC контейнеры поддерживают передачу параметров в конструкторы при вызове Resolve.

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

    ОтветитьУдалить
  31. Спасибо. Лучше это передавать в конструктор - параметр обязательный.

    ОтветитьУдалить

Моя книга «Антихрупкость в IT»

Как достигать результатов в IT-проектах в условиях неопределённости. Подробнее...