Написание хорошего слоя доступа к данным – это не тривиальная задача. Примеров реализации невероятно много, но адекватных среди них единицы.
Можно ли считать реализацию шаблона Repository — DAL?
Вот что предлагают MS msdn.microsoft.com/en-us/library/ff649690.aspx
А вот и местные работы habrahabr.ru/post/52173
Варианты довольно нормальные.
Но когда я вижу
«Репозиторий – это фасад для доступа к базе данных.»…
Я предпочитаю деление Domain Layer – Repository – Storage Layer (всего лишь термины)
Участники слоев
Domain Layer
Aggregation root
— martinfowler.com/bliki/DDD_Aggregate.html (по простому — объект предметной модели о котором знает Repository)Query
(собственно о нем речь) – обязательно формируется в терминах Предметной областиRepository
Его величество
Repository
— martinfowler.com/eaaCatalog/repository.htmlMapper
— martinfowler.com/eaaCatalog/mapper.html (знает как преобразовать Storage entity в Aggregation root и на оборот)UoW
— martinfowler.com/eaaCatalog/unitOfWork.html (без этой штуки, нет права на ошибку, а с ней есть)Storage Layer
Storage entity
– об этом коротко не скажешь.И так DAL это 6 (5 если Mapper считать частью Repository) блоков, каждый из которых играет важную роль.
Запрос – всего лишь параметры фильтрации. В качестве запроса передаваемому в Repository мне очень нравится идея Specification www.codeproject.com/Articles/670115/Specification-pattern-in-Csharp Так же есть habrahabr.ru/post/171559.
Формировать отдельные классы фильтры – работа так себе, но когда начинаешь все это использовать
query = CarSpecifications.ByMark( mark ).
And( CarSpecifications.ByColor( color ).Not() ) ;
cars = carRepository.Get(query);
Понимаешь, что это того стоило. Часто используемые запросы, для удобства, можно определить в CarSpecifications.
Особо понравился комментарий
Напишите x=>x.GroupName == groupName один раз и засуньте его в статический метод-расширения с говорящим названием:
public class UserQueryExtensions { public static IQueryable<User> WhereGroupNameIs(this IQueryable<User> users, strin name) { return users.Where(u => u.GroupName == name); } }
В 10 раз меньше кода, а результат тот же.
С помощью этого ну очень удобно комбинировать запросы Or и Not. Ну и статика противоречит DI.
С точки зрения проектирования шаблон прекрасен. С помощью маленьких объектов, которые просты для понимания (главное для каждого фильтра создать отдельный класс, а не использовать
new ExpressionSpecification(o => o.BrandName == BrandName.Samsung);)
Кресты, это откат к тому от чего Repository должен был избавить.)
можно формировать сложные фильтры.
Фильтры применяются к объектам предметной области (которые являются так же и Aggregation root).
И все бы было прекрасно, если бы не было так ужасно. То из за чего я НЕНАВИЖУ программирование – суровую действительность отраженную в не совершенстве систем. Specification является предикатом истинность которого определяется исходя из объекта предметной области,
увы и ах, как Repository переведет ISpecification<D> в Predicate<S> и выполнит его на элементах хранилища?
Да ни как.
Но решение очень простое для каждого S выполнить S -> D (маппинг) и уже к D применить спецификацию. И вроде как опять все хорошо, но суровая действительность не отступает, диктуя все новые преграды.
Постановка квеста: есть бд, в ней живет таблица, в таблице 50к записей.
Применяем алгоритм для каждого S, GetAll… вы не ошиблись OutOfMemory.
Подполз муравей к жд путям, и решил «умный в гору не пойдет, умный гору обойдет, я ж не дурак».
В общем муравья так до сих пор и не нашли. Идем по методу муравья, боремся с суровой действительностью. Получаем по 1 объекту
? 1 S, S -> D, IsSatisfiedBy( D )
? 2 S, S -> D, IsSatisfiedBy( D )
? 3 S, S -> D, IsSatisfiedBy( D )
? K
? 50000 S, S -> D, IsSatisfiedBy( D )
Поздравляю мы выполнили
50к запросов к бд + 50к применили функцию преобразования + 50к * n спецификаций в переданной цепочке, и все это = БЕСКОНЕЧНОСТИ
(клиент ответа не дождется, в лучшем случае умрет от старости, в худшем уйдет к конкурентам) и все это ради пары объектов удовлетворяющим критерию поиска (за то работает, если конечно SQL не умрет от Dos покушения).
Это конец?
Конечно нет, прокачиваем алгоритм запросы к бд делаем порциями, запрашиваем по m элементов в каждом запросе, теперь мы выполнили
50к / m запросов к бд + 50к применили функцию преобразования + 50к * n спецификаций в переданной цепочке, что так же = БЕСКОНЕЧНОСТИ.
Я иссяк, я пересох, я сдался.
На этом суровая реальность в лице времени и несовершенства систем все ж одолела. Яркий пример когда технологии сдерживают отличное проектирование… На фото выглядит замечательно, в реале не особо (это мягко сказано).
P.S.
Спецификации можно применять на небольших наборах данных, например если известно, что объектов будет не много и GetAll S, S -> D выполняется за приемлемое время, а так же если есть возможность сделать полный кэш данных в память и дальше просто применять IsSatisfiedBy( D ).
Комментарии (186)
Nagg
21.06.2015 11:19Писал когда-то статью по спецификациям тут на хабре. У меня там IsSatisfiedBy имел возращающее значение
а неExpression<Func<T, bool>>
илиPredicate<T>
что давало возможность репозиторию использовать его в фильтрах без маппингов и загрузки всей таблицы в память.Func<T, bool>
PS: не течёт. немножко подтекает ;-)InWake Автор
21.06.2015 12:23Я на нее ссылаюсь в своем описание, именно ваша работа с подвигла написать этот пост. Expression<Func<T, bool>> это конечно круто, но когда речь идет об объектах предметной области (так же они являются Aggregation root) вы попробуйте написать адекватный Expression. Объект хранилища может не обладать свойствами объекта предметной области как решить эту проблему?
gandjustas
21.06.2015 20:28Data Access Layer в .net уже года три как полностью закрывается EF. Больше ничего писать не нужно. Более того, любой дополнительный код скорее вреден.
Nagg
21.06.2015 22:36+1Каждый для себя сам решает — нужна ли ему абстракция повверх технологии хранения данных или нет.
gandjustas
22.06.2015 00:06Тем не менее пользы от абстракции в большинстве случаев не будет, а в некоторых случаях будет вред.
Nagg
22.06.2015 00:33+1«в большинстве случаев» — слишком абстрактно (кол-во однокоренных слов к «абстракция» зашкаливает в этом треде: Р). Лично участвовал в проекте, в котором EF меняли на nHibernate и благодаря абстракции это прошло безболезненно.
gandjustas
22.06.2015 00:41-1А зачем меняли?
Вообще EF имеет в разы больше возможностей по генерации запросов, значит вы его банально не использовали, поэтому и прошло безболезненно. А если не использовали эти возможности, то ваше приложение работало (и сейчас работает) гораздо медленнее, чем могло бы.
Короче вы подтвердили, что абстракция несла вред. И вам очень повезло, что хоть какая-то польза от нее была.Nagg
22.06.2015 00:48+1На тот момент nHibernate на голову превосходил по мощности EF (4ой версии), хотя есть мнение что и сейчас. Вот статейка, в которой человек утверждает, что nHibernate всё еще лучше и много удобнее ложиться на DDD: enterprisecraftsmanship.com/2014/11/29/entity-framework-6-7-vs-nhibernate-4-ddd-perspective
PS: минусую не я :-)gandjustas
22.06.2015 01:18Linq в NH сильно отстает даже от EF4, а без Linq все остальные возможности — ненужный фетиш. Что по поводу статьи, то там автор пытается решить проблемы, которые сам же создал. Например я не вижу смысла даже загружать из базы Order, чтобы удалить OrderLine. Более того, если у тебя есть id OrderLine, то можно даже OrderLine не загружать, чтобы его удалить.
InWake Автор
22.06.2015 10:01Я работаю над проектом в котором пользователь сам выбирает где хранить свои данные. И конфигурация DAL происходит при инициализации модуля. Без абстракции это не возможно.
а в некоторых случаях будет вред
от всего может быть вред, если пренебрегать правилами использования. ( касается не только IT сферы, а всего медицины, техники… )
Я полностью согласен с Nagg
InWake Автор
21.06.2015 22:55К сожалению не всегда можно описать сущность хранилища что бы она отражала суть предметной области. Так же EF как и любая современная ORM реализует UoW только частично.
mird
21.06.2015 23:32А не надо это делать на уровне хранилища данных. Суть предметной области должна отображаться на уровне домена.
InWake Автор
22.06.2015 10:04Я об этом и пишу
К сожалению не всегда можно описать сущность хранилища что бы она отражала суть предметной области.
в ответ наData Access Layer в .net уже года три как полностью закрывается EF. Больше ничего писать не нужно. Более того, любой дополнительный код скорее вреден.
lair
22.06.2015 11:19+1Так какая вам разница, какое хранилище, если все равно сущности в хранилище и в домене будут различаться?
gandjustas
22.06.2015 00:27Суть предметной области не в данных, а в операциях. У Липперта есть замечательный цикл статей на эту тему ericlippert.com/2015/04/27/wizards-and-warriors-part-one (и еще 4 части, причем последняя часть ключевая).
Основная идея, что надо отделить состояние от операций. Операции — это и есть предметная область, которой надо уделять много внимания. А состояние — вопрос удобства программиста и не более того. Так вот EF прекрасно работает с состоянием системы, хранимом в БД.
UoW кстати не сильно нужен для управления состоянием. Так как массовые операции делаются с помощью DML-запросов, а для единичных изменений вполне хватает того, что предлагает EF.
Nagg
22.06.2015 00:28В теории вам не нужны дополнительные сущности для хранение — вы можете использовать доменные + EF mapping (ведь EF'у не обязательно нужны всякие атрибуты и т.п., но есть небольшие ограничения над сущностями (типа обязательного paramterless конструктора) что опять же чуть-чуть подтекающая абстракция ;-)
lair
Поздравляю, теперь вы тоже знаете, что все абстракции текут.
InWake Автор
lair
Да ладно, это же intrinsic challenge. Если он вам не нравится, вы как-то не в той профессии.
InWake Автор
Да. Я давно склоняюсь к тому, что думать — очень вредно для здоровья.