Лучшая практика шаблона репозитория

It’s двойной оператор двоеточия :: (см. список маркеров синтаксического анализатора ).

22
задан Jason N. Gaylord 28 August 2009 в 15:20
поделиться

3 ответа

Strictly speaking, a Repository offers collection semantics for getting/putting domain objects. It provides an abstraction around your materialization implementation (ORM, hand-rolled, mock) so that consumers of the domain objects are decoupled from those details. In practice, a Repository usually abstracts access to entities, i.e., domain objects with identity, and usually a persistent life-cycle (in the DDD flavor, a Repository provides access to Aggregate Roots).

A minimal interface for a repository is as follows:

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

Although you'll see naming differences and the addition of Save/SaveOrUpdate semantics, the above is the 'pure' idea. You get the ICollection Add/Remove members plus some finders. If you don't use IQueryable, you'll also see finder methods on the repository like:

FindCustomersHavingOrders();
FindCustomersHavingPremiumStatus();

There are two related problems with using IQueryable in this context. The first is the potential to leak implementation details to the client in the form of the domain object's relationships, i.e., violations of the Law of Demeter. The second is that the repository acquires finding responsibilities that might not belong to the domain object repository proper, e.g., finding projections that are less about the requested domain object than the related data.

Additionally, using IQueryable 'breaks' the pattern: A Repository with IQueryable may or may not provide access to 'domain objects'. IQueryable gives the client a lot of options about what will be materialized when the query is finally executed. This is the main thrust of the debate about using IQueryable.

Regarding scalar values, you shouldn't be using a repository to return scalar values. If you need a scalar, you would typically get this from the entity itself. If this sounds inefficient, it is, but you might not notice, depending on your load characteristics/requirements. In cases where you need alternate views of a domain object, because of performance reasons or because you need to merge data from many domain objects, you have two options.

1) Use the entity's repository to find the specified entities and project/map to a flattened view.

2) Create a finder interface dedicated to returning a new domain type that encapsulates the flattened view you need. This wouldn't be a Repository because there would be no Collection semantics, but it might use existing repositories under the covers.

One thing to consider if you use a 'pure' Repository to access persisted entities is that you compromise some of the benefits of an ORM. In a 'pure' implementation, the client can't provide context for how the domain object will be used, so you can't tell the repository: 'hey, I'm just going to change the customer.Name property, so don't bother getting those eager-loaded references.' On the flip side, the question is whether a client should know about that stuff. It's a double-edged sword.

As far as using IQueryable, most people seem to be comfortable with 'breaking' the pattern to get the benefits of dynamic query composition, especially for client responsibilities like paging/sorting. In which case, you might have:

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

and you can then do away with all those custom Finder methods, which really clutter the Repository as your query requirements grow.

34
ответ дан 29 November 2019 в 04:26
поделиться

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

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

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

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

8
ответ дан 29 November 2019 в 04:26
поделиться

Относительно 1: As far as I can see it, it is not the IQuerable itself that is the problem being returned from a repository. The point of a repository is that it should look like an object that contains all your data. So you can ask the repository for the data. If you have more than one object needing the same data, the job of the repository is to cache the data, so the two clients of your repository will get the same instances - so if the one client changes a property, the other will see that, becuase they are pointing to the same instance.

If the repository was actually the Linq-provider itself, then that would fit right in. But mostly people just let the Linq-to-sql provider's IQuerable pass right through, which in effect bypasses the responsibility of the repository. So the repository isn't a repository at all, at least according to my understanding and usage of the pattern.

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

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

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

4
ответ дан 29 November 2019 в 04:26
поделиться
Другие вопросы по тегам:

Похожие вопросы: