Pull to refresh

Comments 24

То, что в примерах называется RavenSession — по сути репозиторий.

Интересно, что будет при таком подходе, когда вместо того, чтобы поменять в одном месте реализацию, например, FindFree, придется менять LINQ запрос во всех местах где он используется.

Ну и про unit of work полезно помнить тоже.
Ну так автор вроде как и предлагает не сооружать вокруг него еще один слой репозитория.

По второму — я такие случаи решаю выносом LINQ выражений в extension methods к IQueryable, или ещe какие-нибудь объекты-запросы. Но тоже не буду пытаться абстрагироваться от ORM в большинстве случаев. Хотя бывают датацетричные приложения, которые этого требуют.
«Не сооружать репозиторий» приводит к обозначенным проблемам.

Чаще всего написать экстеншен метод вы решите после пары итераций рефакторинга уже написанного кода.

ну и на мой взгляд — экстеншен, в данном случае, это гораздо более извращенный подход, чем репозиторий.

К каким обозначенным? Если изменить FindFree — то он у меня уже есть в моих extension методах- я его в одном месте меняю. Ну только в этих extension методах я не пытаюсь создать обертки вокруг SingleOrDefault и других тривиальных случаев, чем придется заниматься репозиторию
И более того — такие IQueryable estensions позволяют объединять их в цепочки:
например, var userActivePosts = posts.IsActive().ForUser(user)
Интересная мысль.

По-моему, подобный подход может привести к дубляжу кода запросов. Хорошо, когда запрос — это просто «загрузи все посты». А представьте, если посты могут быть помеченными как «archived» с помощью флага. Пользователь же всегда должен работать с активными постами, и есть редкие случаи, когда ему показываются и архивные посты тоже.

В таком случае надо будет искать все Query к постам и добавлять везде, где надо, .IsNotArchived(), нежели просто поменять методы в репозитории Post. Имхо, это уже дубляж пойдет.
Ну так автор и не предлагает оставить дублицированный код как есть. Просто вместо создания слоя репозитария вокруг ORM (готового репозитария) — использовать более тонкие концепции — вынося этот код в них.

Как я написал выше — для меня так работают доп классы с extension методами к iqueryable, или классы генерирующие деревья запросов.
Единственный минус такого подхода, если вы захотите заменить тот же RavenDB на Redis или SqlServer, при этом выбор механизма хранения будет зависеть от конкретного клиента.
Это в сферическом вакууме можно разработать систему, которая легко переживёт смену провайдера данных. В реальных системах сколько бы абстракций вы не нагородили при смене провайдера придётся много чего допиливать, если, конечно, у вас система не построена только на простейших запросах, которые можно через LINQ все написать (eager fetch, кэш запросов, разные реализации постраничных результатов, batching запросов, query plans hints и т.п.).

Да и сколько раз в жизни вы меняли провайдер данных у работающей системы? :)
Текущий проект уже работает с 7 разными провайдерами =) Но у меня специфическая тема. В основном LDAP или LDAP over SQL и тд. Пока еще дошли руки попробовать свои LINQ провайдеры к ним писать =)
Ну, в специфичных проектах — не спорю. Но я думаю, что подавляющее большинство обычных проектов никогда не меняли и не будут менять провайдера.
Еще зависит от сложности системы и уровней абстракции, так как например возможны репозитории для UI, которые будут инкапсулировать логику создания моделей и тд. А так же возможны ситуации, когда часть логики системы может быть InProc так и Remote, в зависимости от нагрузки и сценариев развертывания.

Меня лично очень напрягли толстые контроллеры в посте, но это ИМХО. Возможно в небольшом проекте это будет ок, но при увеличении сложности, может встать колом, допустим, при аудите изменений объектов и логировании.
Да, про это и речь — зачастую делают репозиторий поверх ORM репозиториев без реальной необходимости. Абстракции «на будущее» это такое же зло как и преждевременная оптимизация.
Более того использование более одного провайдера, это гарантированная деградация производительности и/или удобства. В итоге вместо использования фич. Oracle, приложение ограничено функционалом MySQL. Хотя вся контора давно и плотно сидит на Oracle и имеет кучу ДБА высокой квалификации.

public interface IConferenceRepository
{
    IEnumerable<Conference> FindAll();
    IEnumerable<Conference> FindFuture();
    IEnumerable<Conference> FindFree();
    IEnumerable<Conference> FindPaid();
}


На мой взгляд, данный код в любом случае придется писать в случае клиент-серверного программирования. Вы же не позволите удаленному клиенту вручную управлять базами данных. А вот максимально удобно реализовать указанный выше интерфейс сейчас можно с помощью ASP.NET Web API, ну или WCF(если не ошибаюсь)

Как понимаю, нет смысла писать абстракцию для методов, реализованных в указанном интерфейсе, и именно в коде контроллера размещать запросы Linq.
Есть ещё другой способ — он чуть более «многословный», но вполне элегантный — перевод всех операций вашего домена в форму Command/Query классов.

Проблема больших репозиториев ещё и в том, что они подрывают всю идею dependency injection — скажем, если у вас сервис зависит от какого-нибудь репозитория с кучей методов, то вы никогда не поймёте а какие же именно методы использует сервис пока не посмотрите его код. Соответственно, трудно изолировать injected функционал.

Я уже не говорю о том, что подобные репозитории нарушают SRP.

Вообще, конечно, очень много систем написано подобным способом и они отлично работают. Но в данном случае мы говорим о «полировании» архитектуры системы под конкретные нужды.
По-моему, как раз из-за желания соблюдать SRP и возник паттерн «Репозиторий». Перенося его функциональность (создание запросов) в модель или контроллер мы добавляем им ещё одну ответственность.

А проблема больших репозиториев достаточно легко, имхо, решается декомпозицией, возможно наследованием и параметризацией. Зачастую в репозитории, скажем в UserRepository, создаются методы практически на автомате для каждого запроса, возвращающего объекты User, отличающегося от другого одним-двумя параметрами запроса, например проверкой поля active или archive. То есть получаются репозитории с набором методов, почти дублирующих функциональность друг друга, типа findAll() и findActive(), findById() и findActiveByID и т. п., а ведь можно было бы сделать два репозитория AllUserRepository и ActiveUserRepository и в пару раз сократить количество методов в каждом — общее одинаково, но разделены между двумя классами.
Репозитории которые есть у большинства проектов это в лучшем случае свалка запросов вида от QueryActiveUsers до QueryUserIdsFromRegionByName. Это далеко от SRP в его чистом виде. Конечно, всегда можно сказать, что, мол, repository выполняет одну задачу «выборка информации» и что это SRP — но тогда можно сказать про любой код «он выполняет действия» и это тоже будет SRP.

В общем, если уж заморачиваться с этим делом нормально, то нужно каждый метод расписывать Role Interface-ом и в конечном итоге придём к Commands/Queries классам (к слову, очень достойная архитектура).

Другое дело, что это не всегда оправданно. Вот тут уже нужно задать себе вопрос — а если это не оправданно, то зачем вообще надстраивали свой репозиторий над ORM-ским репозиторием?
Так репозиторий не просто выполняет действия, а вполне определенные — выборку информации определенного типа из хранилища.

Репозитории, емнип, и рекомендуются в случае сложных систем, для приложений сводящихся к CRUD они действительно избыточны. Субъективно оцениваю так: появляется (или точно знаю, что появится) дублирование кода запросов — пора делать репозиторий. Правда ещё один нюанс возникает, требующий принятия решений — если сделал репозиторий для одного типа объектов домена, то надо ли делать для остальных ради однообразия архитектуры.
Ещё нужно не забывать про то, что нам уже зачастую ORM даёт готовый репозиторий и нужно ещё обосновать необходимость новой абстракции поверх этого. Дублирование кода запросов достаточно просто решить и без репозиториев (к примеру, спецификациями или, на крайний случай, методами расширения как предлагал Ayende).
Многое зависит от ORM и от языка. Согласен с
Абстракции «на будущее» это такое же зло как и преждевременная оптимизация.
, но с небольшим уточнением — если точно знаешь, абстракция понадобится завтра, через неделю или через год, то лучше сделать её сейчас. А вот оптимизация может подождать, даже если точно знаешь, что она понадобится. Но вот абстракцию под неё лучше заранее создать :)
Ну, так же можно говорить про оптимизацию, мол, закладывать её нужно на будущее, а то ведь никак.
Абстракция может также подождать до нужного момента — потом рефакторинг.
Архитектура не более. Если пойти по минимальному пути и добавлять «хотелки» уже после, вы добавите себе работы в разы, или даже на порядки.

Чем кстати и плох agile, вроде идея нормальная, но требует крутого спеца, который держит все в голове, и выкладывает по мере необходимости.
Существенные минусы, на мой взгляд: «тяжеловесные» контроллеры, увеличенная связанность, необходимость тестирования логики выборки данных вместе с логикой обработки данных. По моему, этого достаточно, чтобы потратить время на инкапсуляцию.
Sign up to leave a comment.

Articles