Лучшая практика для репозитория - PullRequest
21 голосов
/ 28 августа 2009

Итак, я реализую шаблон репозитория в приложении и натолкнулся на две «проблемы» в моем понимании шаблона:

  1. Запросы - я прочитал ответы, что IQueryable не следует использовать при использовании репозиториев. Однако очевидно, что вы захотите, чтобы вы не возвращали полный список объектов при каждом вызове метода. Должно ли это быть реализовано? Если у меня есть метод IEnumerable под названием List, какова общая «лучшая практика» для IQueryable? Какие параметры должны / не должны иметь?

  2. Скалярные значения. Какой наилучший способ (с использованием шаблона Repository) вернуть одно скалярное значение без необходимости возвращать всю запись? С точки зрения производительности, не будет ли более эффективным возвращать только одно скалярное значение для всей строки?

Ответы [ 3 ]

32 голосов
/ 28 августа 2009

Строго говоря, репозиторий предлагает семантику коллекции для получения / размещения объектов домена. Он обеспечивает абстракцию вокруг вашей реализации материализации (ORM, свернутая вручную, фиктивная), так что потребители доменных объектов отделены от этих деталей. На практике репозиторий обычно абстрагирует доступ к сущностям, то есть объектам домена с идентичностью и, как правило, постоянным жизненным циклом (в варианте DDD репозиторий предоставляет доступ к агрегированным корням).

Минимальный интерфейс для хранилища следующий:

void Add(T entity);
void Remove(T entity);
T GetById(object id);
IEnumerable<T> Find(Specification spec);

Несмотря на то, что вы увидите различия в именах и добавление семантики Save / SaveOrUpdate, вышеприведенная идея является «чистой» идеей. Вы получаете ICollection Add / Remove members плюс некоторые искатели. Если вы не используете IQueryable, вы также увидите методы поиска в репозитории, такие как:

FindCustomersHavingOrders();
FindCustomersHavingPremiumStatus();

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

Кроме того, использование IQueryable «ломает» шаблон: репозиторий с IQueryable может предоставлять или не предоставлять доступ к «объектам домена». IQueryable предоставляет клиенту множество опций о том, что будет реализовано, когда запрос будет наконец выполнен. Это основной смысл дискуссии об использовании IQueryable.

Что касается скалярных значений, вам не следует использовать репозиторий для возврата скалярных значений. Если вам нужен скаляр, вы, как правило, получите это от самой сущности. Если это звучит неэффективно, это так, но вы можете не заметить, в зависимости от ваших характеристик нагрузки / требований В случаях, когда вам нужны альтернативные представления объекта домена, из-за соображений производительности или из-за необходимости объединения данных из множества объектов домена, у вас есть два варианта.

1) Используйте репозиторий сущностей, чтобы найти указанные сущности, и проект / карту в плоском виде.

2) Создайте интерфейс поиска, предназначенный для возврата нового типа домена, который инкапсулирует нужное вам плоское представление. Это не был бы репозиторий, потому что не было бы семантики коллекции, но он мог бы использовать существующие репозитории под обложками.

Если вы используете «чистый» репозиторий для доступа к постоянным сущностям, следует учитывать одну вещь: вы подвергаете риску некоторые из преимуществ ORM. В «чистой» реализации клиент не может предоставить контекст для того, как будет использоваться объект домена, поэтому вы не можете сообщить хранилищу: «эй, я просто собираюсь изменить свойство customer.Name, так что не беспокойтесь о тех загруженных ссылках. С другой стороны, вопрос заключается в том, должен ли клиент знать об этом материале. Это обоюдоострый меч.

Что касается использования IQueryable, большинству людей, кажется, удобно «нарушать» шаблон, чтобы получить преимущества динамической композиции запросов, особенно для таких обязанностей клиента, как разбиение на страницы / сортировка. В этом случае вы можете иметь:

Add(T entity);
Remove(T entity);
T GetById(object id);
IQueryable<T> Find();

и вы можете покончить со всеми этими пользовательскими методами Finder, которые действительно загромождают репозиторий по мере роста требований к запросу.

8 голосов
/ 28 августа 2009

В ответ на @lordinateur мне не очень нравится дефакто-способ указать интерфейс репозитория.

Поскольку для интерфейса в вашем решении требуется, чтобы каждая реализация репозитория требовала хотя бы Add, Remove, GetById и т. Д. Теперь рассмотрим сценарий, в котором не имеет смысла сохранять данные через конкретный экземпляр репозитория. должны реализовать оставшиеся методы с NotImplementedException или что-то в этом роде.

Я предпочитаю разделять объявления интерфейса репозитория следующим образом:

interface ICanAdd<T>
{
    T Add(T entity);
}

interface ICanRemove<T>
{
    bool Remove(T entity);
}

interface ICanGetById<T>
{
    T Get(int id);
}

Таким образом, конкретная реализация репозитория для сущности SomeClass может выглядеть следующим образом:

interface ISomeRepository
    : ICanAdd<SomeClass>, 
      ICanRemove<SomeClass>
{
    SomeClass Add(SomeClass entity);
    bool Remove(SomeClass entity);
}

Давайте сделаем шаг назад и посмотрим, почему я считаю, что это более эффективная практика, чем реализация всех методов CRUD в одном универсальном интерфейсе.

Некоторые объекты имеют разные требования, чем другие. Клиент объект не может быть удален, PurchaseOrder не может быть обновлен, и Объект ShoppingCart может быть только создано. Когда один использует общий Интерфейс IRepository это очевидно вызывает проблемы в реализация.

Те, кто реализует анти-шаблон часто будут реализовывать их в полном объеме интерфейс тогда скинет исключения за методы, которые они не служба поддержки. Помимо несогласия с многочисленные принципы ОО это нарушает их надежда на возможность использовать их Эффективная абстракция если они также не начинают ставить методы на нем для того, чтобы дать или нет данные объекты поддерживаются и в дальнейшем реализуются им.

Обходное решение этой проблемы: перейти к более детальным интерфейсам такие как ICanDelete, ICanUpdate, ICanCreate и т. Д. И т. Д. Это пока работать вокруг многих проблем которые возникли с точки зрения ОО принципы также значительно уменьшает количество повторного использования кода, который в настоящее время рассматривается как большую часть времени один не будет иметь возможность использовать репозиторий конкретный пример больше.

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

Эти выдержки были позорно взяты из этого поста , где вы также можете прочитать подробное обсуждение в комментариях.

4 голосов
/ 28 августа 2009

Относительно 1: Насколько я понимаю, проблема не в самом IQuerable, который возвращается из хранилища. Смысл репозитория в том, что он должен выглядеть как объект, содержащий все ваши данные. Таким образом, вы можете попросить хранилище данных. Если у вас есть несколько объектов, нуждающихся в одних и тех же данных, задача хранилища заключается в кэшировании данных, поэтому два клиента вашего хранилища получат одинаковые экземпляры - поэтому, если один клиент изменит свойство, другой увидит, что потому что они указывают на один и тот же экземпляр.

Если бы репозиторий был на самом деле самим провайдером Linq, то он бы подходил. Но в основном люди просто пропускают сквозь IQuerable провайдера Linq-to-sql, что фактически обходит ответственность репозитория. Таким образом, хранилище вообще не является хранилищем, по крайней мере, в соответствии с моим пониманием и использованием шаблона.

Относительно 2: Естественно, более эффективно с точки зрения производительности просто возвращать одно значение из базы данных, чем всю запись. Но используя шаблон репозитория, вы не будете возвращать записи вообще, вы будете возвращать бизнес-объекты. Таким образом, логика приложения должна касаться не полей, а объектов домена.

Но насколько эффективнее вернуть одно значение по сравнению с полным доменным объектом? Вероятно, вы не сможете измерить разницу, если схема базы данных достаточно хорошо определена.

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

...