Как лучше всего спроектировать generic репозиторий на базе EF Code First
От: DmytroL Украина http://www.linkedin.com/in/dmytrol
Дата: 03.06.14 08:28
Оценка: -1
Всем привет,

Поиском по форуму ответа не нашел, в Интернетах предлагают самые различные варианты решений, к сожалению, без анализа плюсов/минусов. Поэтому обращаюсь за советом к сообществу.

Итак, чего хочется:


  1. Абстрагировать Entity Framework от всего остального кода в виде интерфейсов Repository / Unit of Work (в далеком будущем не исключен вариант замены связки EF + SQL Server на, например, NoSQL хранилище, поэтому очень не хочется завязывать остальной код на специфичные для EF интерфейсы)
  2. Полная изоляция бизнес-логики в целях юнит-тестирования (т.е. создаем mock'и для IRepository / IUnitOfWork)
  3. Репозиторий не должен знать ничего о бизнес-логике (кол-во сущностей, специфические запросы для определенной сущности и т.п.), т.е. должен быть полностью обобщённым (generic)

Собственно, последний пункт вызывает больше всего вопросов, так как большинство примеров, которые я находил в Интернете, предлагают примерно такие варианты:


interface IUnitOfWork
{
  IRepository<Customer> Customers { get; }
  IRepository<Order> Orders { get; }
  // и т.п.
  void Commit();
}


а то и

interface IUnitOfWork
{
  ICustomerRepository Customers { get; }
  IOrderRepository Orders { get; }
  // и т.п.
  void Commit();
}


Которые плохи "протеканием" знаний о бизнес-сущностях в слой инфраструктуры. А вариант наподобие такого


interface IUnitOfWork
{
  IRepository<T> Repository { get; }
  void Commit();
}


встречается очень редко. А если и встречается, то в подобной реализации IUnitOfWork ручками кэшируются ссылки на DbSet<T> (хотя, вроде бы, DbContext сам должен это делать? )

Кроме этого, в примерах по Code First довольно часто создается класс-наследник DbContext, который, опять-таки, обладает знаниями о бизнес-логике:


public class MyDbContext: DbContext
{
  // ...
  public DbSet<Customer> Customers { get; }
  // ...
}


Это какое-то требование EF Code First — объявлять все возможные варианты параметризации DbSet как члены класса-наследника DbContext, или так делают просто для удобства, не задумываясь о low coupling и single responsibility principle ?

В общем, я немного запутался — наставьте, пожалуйста, на путь истинный!

Спасибо!
code first entity framework domain driven design oop
Re: Как лучше всего спроектировать generic репозиторий на базе EF Code First
От: DmytroL Украина http://www.linkedin.com/in/dmytrol
Дата: 03.06.14 08:40
Оценка:
Важное уточнение: Предполагается CQRS-подобный подход, поэтому, в плане вычитки, в репозитории будет реализована только возможность FindByID. Все сложные запросы на вычитку будут отрабатываться отдельной рид-моделью на основе другого, легковесного ORM.
Re: Как лучше всего спроектировать generic репозиторий на базе EF Code First
От: hardcase Пират http://nemerle.org
Дата: 03.06.14 09:22
Оценка: 1 (1) +2
Здравствуйте, DmytroL, Вы писали:

DL>
  • Абстрагировать Entity Framework от всего остального кода в виде интерфейсов Repository / Unit of Work

    Какой смысл абстрагировать абстракцию?
  • /* иЗвиНите зА неРовнЫй поЧерК */
    Re[2]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: DmytroL Украина http://www.linkedin.com/in/dmytrol
    Дата: 03.06.14 09:58
    Оценка:
    Здравствуйте, hardcase, Вы писали:

    DL>>
  • Абстрагировать Entity Framework от всего остального кода в виде интерфейсов Repository / Unit of Work
    H>Какой смысл абстрагировать абстракцию?

    Смысл — заложить возможность полной смены технологии доступа к данным (например — переход на NoSQL или движок БД, с которым не умеет работать EF), не затрагивая остальной код системы
  • Re[3]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: hardcase Пират http://nemerle.org
    Дата: 03.06.14 10:08
    Оценка: 1 (1) +2
    Здравствуйте, DmytroL, Вы писали:

    DL>Смысл — заложить возможность полной смены технологии доступа к данным (например — переход на NoSQL или движок БД, с которым не умеет работать EF), не затрагивая остальной код системы


    Готов поспорить, что при такой смене технологий хранения остальной код существенно изменится (что там ACID и транзакциями в предполагаемом NoSQL?).
    /* иЗвиНите зА неРовнЫй поЧерК */
    Re[4]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: DmytroL Украина http://www.linkedin.com/in/dmytrol
    Дата: 03.06.14 10:48
    Оценка:
    Здравствуйте, hardcase, Вы писали:

    H>Готов поспорить, что при такой смене технологий хранения остальной код существенно изменится (что там ACID и транзакциями в предполагаемом NoSQL?).


    Уже был реальный боевой опыт. Если весь остальной код работает через вышеописанные интерфейсы — то при переходе на NoSQL усложняется только реализация самих репозитория и unit of work, поскольку груз поддержки транзакционности и ACID теперь ложится в основном на них.
    Re[5]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: drol  
    Дата: 03.06.14 11:06
    Оценка:
    Здравствуйте, DmytroL, Вы писали:

    DL>Уже был реальный боевой опыт. Если весь остальной код работает через вышеописанные интерфейсы


    А можно пример из этого опыта ? Только какой-нибудь не тривиальный, разумеется.
    Re: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: gandjustas Россия http://blog.gandjustas.ru/
    Дата: 03.06.14 11:19
    Оценка: 19 (2) +1
    Здравствуйте, DmytroL, Вы писали:

    DL>Всем привет,


    DL>Поиском по форуму ответа не нашел, в Интернетах предлагают самые различные варианты решений, к сожалению, без анализа плюсов/минусов. Поэтому обращаюсь за советом к сообществу.


    DL>Итак, чего хочется:



    DL>

      DL>
    1. Абстрагировать Entity Framework от всего остального кода в виде интерфейсов Repository / Unit of Work (в далеком будущем не исключен вариант замены связки EF + SQL Server на, например, NoSQL хранилище, поэтому очень не хочется завязывать остальной код на специфичные для EF интерфейсы)
      DL>
    2. Полная изоляция бизнес-логики в целях юнит-тестирования (т.е. создаем mock'и для IRepository / IUnitOfWork)
      DL>
    3. Репозиторий не должен знать ничего о бизнес-логике (кол-во сущностей, специфические запросы для определенной сущности и т.п.), т.е. должен быть полностью обобщённым (generic)
      DL>

    DL>...


    DL>В общем, я немного запутался — наставьте, пожалуйста, на путь истинный!


    Срочно брось этим заниматься и решай конкретную задачу.


    DL>Спасибо!


    Пожалуйста.
    Re[6]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: DmytroL Украина http://www.linkedin.com/in/dmytrol
    Дата: 03.06.14 14:42
    Оценка:
    Здравствуйте, drol, Вы писали:

    D>А можно пример из этого опыта ? Только какой-нибудь не тривиальный, разумеется.


    Пожалуйста — нетривиальная бизнес-логика работает как раз через описанный в исходном посте generic репозиторий и unit of work. В качестве базы данных используется Redis. Реализация собственно unit of work — полностью самописная, плюс, конечно, пользуемся встроенной поддержкой транзакций в Redis. Сам код полностью абстрагирован от специфики Redis и работает исключительно через эти два интерфейса.
    Re[2]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: DmytroL Украина http://www.linkedin.com/in/dmytrol
    Дата: 03.06.14 14:51
    Оценка:
    Здравствуйте, gandjustas, Вы писали:

    G>Срочно брось этим заниматься и решай конкретную задачу.


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

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

    Возможно, вы знаете лучшее решение — в таком случае, поделитесь им, пожалуйста. С другой стороны, обсуждать подходы из серии "чик-чик и в продакшен" и экстремального программирования (постоянный рефакторинг), наверное, не имеет большого смысла, так как, очевидно, они противоречат самой постановке задачи.
    Re[3]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: gandjustas Россия http://blog.gandjustas.ru/
    Дата: 03.06.14 15:31
    Оценка: 48 (4) +1
    Здравствуйте, DmytroL, Вы писали:

    DL>Здравствуйте, gandjustas, Вы писали:


    G>>Срочно брось этим заниматься и решай конкретную задачу.


    DL>Три пункта, перечисленных в начале старт-поста, собственно, входят в постановку конкретной задачи: полное покрытие логики тестами, тестирование в изоляции от хранилища, возможность максимально безболезненной замены реализации хранилища в будущем.


    Это очень распространенные ошибки.

    "полное покрытие логики тестами"

    Для полного покрытия логики тестами достаточно написать набор тестов, которые покроют все ветвления в исходном коде. Это кстати меньше чем покрытие всех сценариев.

    Пример

    void f(bool a, bool b)
    {
        if(a)
        {
            //some side effect
        }
        else
        {
            //some other side effect
        }
    
        // some code
    
        if(b)
        {
           //some side effect
        }
        else
        {
            //some other side effect
        }
    }


    Для покрытия достаточно написать 2 теста:

    f(true, false)
    
    f(false, true)


    И code coverage покажет 100%, хотя реально будет покрыта половина сценариев.

    Покрыть 100% сценариев физически невозможно из-за исключений, да и смысла в этом нет.


    "тестирование в изоляции от хранилища"
    А у вас есть логика, которая может тестироваться в изоляции от хранилища? Обычно 90% работы приложения — отображение данных. Чаще всего это только доставание данных из базы и отображение в UI. Нет смысла такую "логику" тестировать без хранилища, потому что самая важная часть — запрос.

    При изменении данных тоже далеко не всегда нужно тестировать в отрыве от базы, ибо большая часть "работы" это перекладывание данных, введенных пользователем, в базу. Валидация чаще всего делается встроенными средствами, так что её тестировать тоже нет смысла.

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

    "возможность максимально безболезненной замены реализации хранилища в будущем"
    Это как секс у студентов. Все говорят, но никто этим не занимается. Чаще всего заменить базу (особенно SQL на NoSQL) прозрачно для БЛ не получается, ибо для SQL базы активно используются внешние ключи, джоины и проекции, а в NoSQL всего этого нет и надо ручками делать денормализацию. Это приводит к тому что логика изменяется вплоть до контроллеров (слоя сразу за UI).

    При этом заранее абстрагируя БД теряется возможность пользоваться преимуществами БД, например функциями или материализованными представлениями.


    DL>Наверное, сюда же еще можно добавить — минимизация риска массивного рефакторинга, который мог бы потребоваться в случае изначально неверно выбранного архитектурного решения.


    Еще одна ошибка. Вероятность поправить X строк кода в проекте размера N строк, линейно зависит от N.
    Поэтому лучший способ избежать масштабных изменений — писать как можно меньше кода.

    DL>Возможно, вы знаете лучшее решение — в таком случае, поделитесь им, пожалуйста. С другой стороны, обсуждать подходы из серии "чик-чик и в продакшен" и экстремального программирования (постоянный рефакторинг), наверное, не имеет большого смысла, так как, очевидно, они противоречат самой постановке задачи.


    Делюсь:
    1) Написать простой код, который решает бизнес-задачу. Например если это asp.net mvc, то все пишется прямо в методе контроллера.
    2) С помощью extract method выделить то, что касается доступа к данным и их обработки. Оставить в контроллере валидацию входных данных, формирование данных для отображения и обработку ошибок.
    3) После получения набора методов, помогающих решить одну задачу, переносишь их в отдельный класс. А экземпляр этого класса подсовываешь через IoC.
    4) Внутри полученного класса проводишь рефакторинг, выделяя общие куски в отдельные функции.
    5) Если полученные функции не являются тривиальными (2-3 строки), то можно повторить п3-4
    Re[4]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: Аноним  
    Дата: 04.06.14 14:27
    Оценка: -7 :))
    G>Делюсь:
    G>1) Написать простой код, который решает бизнес-задачу. Например если это asp.net mvc, то все пишется прямо в методе контроллера.
    G>2) С помощью extract method выделить то, что касается доступа к данным и их обработки. Оставить в контроллере валидацию входных данных, формирование данных для отображения и обработку ошибок.
    G>3) После получения набора методов, помогающих решить одну задачу, переносишь их в отдельный класс. А экземпляр этого класса подсовываешь через IoC.
    G>4) Внутри полученного класса проводишь рефакторинг, выделяя общие куски в отдельные функции.
    G>5) Если полученные функции не являются тривиальными (2-3 строки), то можно повторить п3-4

    Неожиданный совет от MVPшниника, не сайты-визитки ли вы разрабатываете таким образом?

    G>А у вас есть логика, которая может тестироваться в изоляции от хранилища? Обычно 90% работы приложения — отображение данных. Чаще всего это только доставание данных из базы и отображение в UI. Нет смысла такую "логику" тестировать без хранилища, потому что самая важная часть — запрос.


    Похоже именно визитки, раз 90% работы приложения у вас приходится на запрос . А как же моделирование, предметная область, доменная логика и тому подобное?

    Не понимаю зачем лезть с советами, если не разбираешься в вопросе, проявляете активность для очередного звания?

    G>А у вас есть логика, которая может тестироваться в изоляции от хранилища?

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

    G>"полное покрытие логики тестами"

    G>Для полного покрытия логики тестами достаточно написать набор тестов, которые покроют все ветвления в исходном коде. Это кстати меньше чем покрытие всех сценариев.

    К чему эти понты? Автор не писал, что нужно добиться 100% покрытия кода, он написал про полное покрытие логики.

    G>Покрыть 100% сценариев физически невозможно из-за исключений, да и смысла в этом нет.

    Lol. Если руки растут из правильного места, то все можно. Исключения — это тоже сценарий и его тоже нужно тестировать и покрывать тестами, для этого даже специальные атрибуты есть в юнит тестах .

    G>При этом заранее абстрагируя БД теряется возможность пользоваться преимуществами БД, например функциями или материализованными представлениями.

    "Чукча не читатель, чукча писатель."
    Тем не менее автор написал про то, что использует CQRS подход и что от репозитория ему нужны 2 функции: GetById и Save. К чему же он это написал? Возможно дизайн его системы построен таким образом, что больше ничего от хранилища ему не нужно? Может даже он целенаправленно это сделал? Если например взять и подумать, любой ли сторадж сможет предоставить эти 2 функции, мы будем приятно удивлены, что даже на базе файловой системы без труда можно реализовать репозиторий с таким интерфейсом. Дак неужели настолько не реально заменить один сторадж другим имея такой интерфейс?

    G>Поэтому лучший способ избежать масштабных изменений — писать как можно меньше кода.

    Отличный совет: не делаешь — не ошибаешься . Лучше просто не писать систему и иметь ноль строк кода, тем самым минимизировать затраты на рефакторинг.

    З.Ы.
    Нужно много писать всяких советов на разных ресурсах, тогда тебе дадут налепку, которую можно будет тулить во все посты — это пусть к успеху. Кому нужно учиться и работать...
    Re[5]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: andyag  
    Дата: 12.06.14 00:38
    Оценка: 15 (1) +4
    Здравствуйте, Аноним, Вы писали:

    G>>Делюсь:

    G>>1) Написать простой код, который решает бизнес-задачу. Например если это asp.net mvc, то все пишется прямо в методе контроллера.
    G>>2) С помощью extract method выделить то, что касается доступа к данным и их обработки. Оставить в контроллере валидацию входных данных, формирование данных для отображения и обработку ошибок.
    G>>3) После получения набора методов, помогающих решить одну задачу, переносишь их в отдельный класс. А экземпляр этого класса подсовываешь через IoC.
    G>>4) Внутри полученного класса проводишь рефакторинг, выделяя общие куски в отдельные функции.
    G>>5) Если полученные функции не являются тривиальными (2-3 строки), то можно повторить п3-4

    А>Неожиданный совет от MVPшниника, не сайты-визитки ли вы разрабатываете таким образом?


    Есть несколько стадий развития программиста:

    1. Те, кто пишет реализацию БЛ в контроллере и думает, что это нормально.
    2. Те, кто уже понял, что писать реализацию БЛ в контроллере — это хреново, и осознанно пишут её где-то там в СЛ.
    3. Те, кто понял, что п.1 и п.2 — это крайние варианты решения проблемы сложности, и научился эту самую сложность видеть и направлять её.

    Вы сейчас находитесь в п.2, а товарищ gandjustas находится в п.3. Он просто не воспользовался средствами форматирования, чтобы подчеркнуть, что написал он вам не о том, что логику нужно писать в контроллере, а о том, что надо рефакторить каждые 20 минут и тогда архитектура "появится" сама собой. Слово "появится" в кавычках потому что на самом деле она не появляется, а присуствует всегда. "Хорошая архитектура" — это не константа, а функция от этапа жизненного цикла проекта.
    Re[2]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: Vladek Россия Github
    Дата: 12.06.14 08:59
    Оценка:
    Здравствуйте, DmytroL, Вы писали:

    DL>Важное уточнение: Предполагается CQRS-подобный подход, поэтому, в плане вычитки, в репозитории будет реализована только возможность FindByID. Все сложные запросы на вычитку будут отрабатываться отдельной рид-моделью на основе другого, легковесного ORM.


    Важное уточнение — это не репозиторий. Это Table Gateway, читайте Фаулера внимательнее.

    http://martinfowler.com/eaaCatalog/tableDataGateway.html
    Re[6]: Как лучше всего спроектировать generic репозиторий на базе EF Code First
    От: Dog  
    Дата: 12.06.14 10:17
    Оценка:
    A>Вы сейчас находитесь в п.2, а товарищ gandjustas находится в п.3. Он просто не воспользовался средствами форматирования, чтобы подчеркнуть, что написал он вам не о том, что логику нужно писать в контроллере, а о том, что надо рефакторить каждые 20 минут и тогда архитектура "появится" сама собой. Слово "появится" в кавычках потому что на самом деле она не появляется, а присуствует всегда. "Хорошая архитектура" — это не константа, а функция от этапа жизненного цикла проекта.
    Что-то эта архитехтура очень напоминает мне чукотску песню.
    ... << RSDN@Home 1.2.0 alpha 5 rev. 1539>>
     
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.