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 ).

Комментарии (186)


  1. lair
    21.06.2015 02:06
    +5

    Поздравляю, теперь вы тоже знаете, что все абстракции текут.


    1. InWake Автор
      21.06.2015 12:24

      То из за чего я НЕНАВИЖУ программирование


      1. lair
        21.06.2015 12:41
        +2

        Да ладно, это же intrinsic challenge. Если он вам не нравится, вы как-то не в той профессии.


        1. InWake Автор
          21.06.2015 13:21
          -2

          Да. Я давно склоняюсь к тому, что думать — очень вредно для здоровья.


  1. Nagg
    21.06.2015 11:19

    Писал когда-то статью по спецификациям тут на хабре. У меня там IsSatisfiedBy имел возращающее значение

    Expression<Func<T, bool>>
    а не
    Predicate<T>
    или
    Func<T, bool>
    что давало возможность репозиторию использовать его в фильтрах без маппингов и загрузки всей таблицы в память.
    PS: не течёт. немножко подтекает ;-)


    1. InWake Автор
      21.06.2015 12:23

      Я на нее ссылаюсь в своем описание, именно ваша работа с подвигла написать этот пост. Expression<Func<T, bool>> это конечно круто, но когда речь идет об объектах предметной области (так же они являются Aggregation root) вы попробуйте написать адекватный Expression. Объект хранилища может не обладать свойствами объекта предметной области как решить эту проблему?


    1. mird
      21.06.2015 12:48
      +1

      Подтекает она пока не копаешь :) А как копнешь — так водопад.


  1. gandjustas
    21.06.2015 20:28

    Data Access Layer в .net уже года три как полностью закрывается EF. Больше ничего писать не нужно. Более того, любой дополнительный код скорее вреден.


    1. Nagg
      21.06.2015 22:36
      +1

      Каждый для себя сам решает — нужна ли ему абстракция повверх технологии хранения данных или нет.


      1. gandjustas
        22.06.2015 00:06

        Тем не менее пользы от абстракции в большинстве случаев не будет, а в некоторых случаях будет вред.


        1. Nagg
          22.06.2015 00:33
          +1

          «в большинстве случаев» — слишком абстрактно (кол-во однокоренных слов к «абстракция» зашкаливает в этом треде: Р). Лично участвовал в проекте, в котором EF меняли на nHibernate и благодаря абстракции это прошло безболезненно.


          1. gandjustas
            22.06.2015 00:41
            -1

            А зачем меняли?

            Вообще EF имеет в разы больше возможностей по генерации запросов, значит вы его банально не использовали, поэтому и прошло безболезненно. А если не использовали эти возможности, то ваше приложение работало (и сейчас работает) гораздо медленнее, чем могло бы.

            Короче вы подтвердили, что абстракция несла вред. И вам очень повезло, что хоть какая-то польза от нее была.


            1. 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: минусую не я :-)


              1. gandjustas
                22.06.2015 01:18

                Linq в NH сильно отстает даже от EF4, а без Linq все остальные возможности — ненужный фетиш. Что по поводу статьи, то там автор пытается решить проблемы, которые сам же создал. Например я не вижу смысла даже загружать из базы Order, чтобы удалить OrderLine. Более того, если у тебя есть id OrderLine, то можно даже OrderLine не загружать, чтобы его удалить.


        1. InWake Автор
          22.06.2015 10:01

          Я работаю над проектом в котором пользователь сам выбирает где хранить свои данные. И конфигурация DAL происходит при инициализации модуля. Без абстракции это не возможно.

          а в некоторых случаях будет вред
          от всего может быть вред, если пренебрегать правилами использования. ( касается не только IT сферы, а всего медицины, техники… )

          Я полностью согласен с Nagg


    1. InWake Автор
      21.06.2015 22:55

      К сожалению не всегда можно описать сущность хранилища что бы она отражала суть предметной области. Так же EF как и любая современная ORM реализует UoW только частично.


      1. mird
        21.06.2015 23:32

        А не надо это делать на уровне хранилища данных. Суть предметной области должна отображаться на уровне домена.


        1. InWake Автор
          22.06.2015 10:04

          Я об этом и пишу

          К сожалению не всегда можно описать сущность хранилища что бы она отражала суть предметной области.

          в ответ на
          Data Access Layer в .net уже года три как полностью закрывается EF. Больше ничего писать не нужно. Более того, любой дополнительный код скорее вреден.


          1. lair
            22.06.2015 11:19
            +1

            Так какая вам разница, какое хранилище, если все равно сущности в хранилище и в домене будут различаться?


      1. gandjustas
        22.06.2015 00:27

        Суть предметной области не в данных, а в операциях. У Липперта есть замечательный цикл статей на эту тему ericlippert.com/2015/04/27/wizards-and-warriors-part-one (и еще 4 части, причем последняя часть ключевая).

        Основная идея, что надо отделить состояние от операций. Операции — это и есть предметная область, которой надо уделять много внимания. А состояние — вопрос удобства программиста и не более того. Так вот EF прекрасно работает с состоянием системы, хранимом в БД.

        UoW кстати не сильно нужен для управления состоянием. Так как массовые операции делаются с помощью DML-запросов, а для единичных изменений вполне хватает того, что предлагает EF.


      1. Nagg
        22.06.2015 00:28

        В теории вам не нужны дополнительные сущности для хранение — вы можете использовать доменные + EF mapping (ведь EF'у не обязательно нужны всякие атрибуты и т.п., но есть небольшие ограничения над сущностями (типа обязательного paramterless конструктора) что опять же чуть-чуть подтекающая абстракция ;-)