2009-08-28 6 views
20

Так я реализующий шаблон репозитория в приложении и наткнулся на две «проблемы» в моем понимании картины:Repository Pattern Best Practice

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

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

ответ

30

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

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

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

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

FindCustomersHavingOrders(); 
FindCustomersHavingPremiumStatus(); 

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

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

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

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

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

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

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

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

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

+1

Я столкнулся с сообщением jbogard на эту тему сегодня: http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/09/02/ddd-repository-implementation-patterns.aspx – pfries

+3

+1: ". .. Кажется, что людям очень удобно «ломать» шаблон, чтобы получить преимущества динамической композиции запросов ... » –

3

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

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

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

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

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

7

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

Поскольку интерфейс в вашем решении требует, чтобы каждая реализация репозитория требовала, по крайней мере, 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 это , очевидно, вызывает проблемы в реализации .

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

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

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

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

+0

Что бы не хранилище, которое вы не« сохраняете », похоже? Если вы удалите часть «Добавить/Удалить», у вас будет только интерфейс Finder. Если все, что вы хотите сделать, это найти материал, вам не нужен репозиторий, а скорее какая-то реализация ICustomerFinder.Тем не менее, ваш репозиторий может реализовать Finder: интерфейса ICustomerRepository: IRepository , ICustomerFinder Просто чтобы прояснить, вы на самом деле не «Сохранить» через интерфейс хранилища, который будет нести ответственность за какое-то единицу работы реализация. Такой сеанс лучше всего управлять за пределами репозитория. – pfries

+0

Точка Грега о композиции хорошая, т. Е. Вы можете реализовать репозиторий объектов домена с внутренним общим хранилищем. Вот как это работает на практике при использовании интерфейса, такого как ICustomerRepository, вокруг ORM, например NHibernate (который заменяет общий репозиторий). Его основной задачей является не использование общих контрактов в швах вашего приложения. Это довольно разумно; если ваш контракт является ICustomerRepository, а не IRepository, вы избегаете проблем, которые он описывает. Я не люблю всех искателей, даже если они выражают намерение. Что относительно объектов запроса? – pfries

+0

froghammr: после всех моих исследований я поселюсь с объектами запросов. Я разрабатываю мобильное приложение в MonoDroid/C# и не имею LINQ, Entity или Hibernate, поэтому я должен писать все с нуля. Несмотря на это, накладные расходы Finder/Specification являются глубокими. – samosaris