Pull to refresh

Прогноз на Specification pattern в Domain layer — ожидаются проблемы

Reading time 3 min
Views 20K
Data Access Layer – одна из наиболее больных тем.
Написание хорошего слоя доступа к данным – это не тривиальная задача. Примеров реализации невероятно много, но адекватных среди них единицы.
Можно ли считать реализацию шаблона Repository — DAL?
Вот что предлагают MS msdn.microsoft.com/en-us/library/ff649690.aspx
image
А вот и местные работы 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.html
Mapper
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 ).
Tags:
Hubs:
+11
Comments 187
Comments Comments 187

Articles