Как правильно использовать паттерн репозиторий?

Мне интересно, как мне группировать свои репозитории? Как и в примерах, которые я видел на asp.net mvc и в моих книгах, они в основном используют один репозиторий для каждой таблицы базы данных. Но это похоже на то, что множество репозиториев заставляет вас позже вызывать множество репозиториев для насмешек и прочего.

Так что я предполагаю, что мне следует сгруппировать их. Однако я не уверен, как их сгруппировать.

Прямо сейчас я сделал репозиторий регистрации, чтобы обрабатывать все мои регистрационные вещи. Однако есть около 4 таблиц, которые мне нужно обновить, и до того, как у меня было 3 репозитория для этого.

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

Одно место может быть логином (проверьте, не истек ли срок действия ключа).

Итак, что я буду делать в этой ситуации? Перепишите код еще раз (прервать СУХОЙ)? Попробуйте объединить эти два репозитория вместе и надейтесь, что ни один из методов не понадобится в какой-то другой момент времени (например, возможно, у меня может быть метод, который проверяет, используется ли userName - возможно, мне это понадобится где-то еще).

Кроме того, если я объединю их вместе, мне понадобятся 2 уровня обслуживания, идущие в один и тот же репозиторий, поскольку я думаю, что вся логика для двух разных частей сайта будет долгой, и мне нужно будет иметь такие имена, как ValidateLogin (), ValdiateRegistrationForm () , ValdiateLoginRetrievePassword () и т. Д.

Или все равно позвонить в Репозиторий и иметь какое-то странно звучащее имя?

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


person chobo2    schedule 30.09.2009    source источник
comment
+1. Отличный вопрос.   -  person griegs    schedule 30.09.2009
comment
Спасибо, что уже некоторое время меня глючит. Поскольку я считаю, что те, что я видел в книге, слишком просты, они не показывают вам, что делать в таких ситуациях.   -  person chobo2    schedule 30.09.2009
comment
Я в основном делаю то же самое, что и вы, и да, если у меня есть класс linq2sql, который используется более чем в одном репозитории, и мне нужно изменить структуру таблицы, я нарушаю DRY. Менее чем в идеале. Теперь я планирую это немного лучше, поэтому мне не нужно использовать класс linq2sql более одного раза, что, я думаю, является хорошим разделением проблем, но я предвижу день, когда это станет для меня настоящей проблемой.   -  person griegs    schedule 30.09.2009
comment
Я задал похожий вопрос (но не идентичный) здесь: stackoverflow.com/questions/910156/   -  person Grokys    schedule 30.09.2009
comment
Да, но это сложно спланировать на все случаи жизни. Как я уже сказал, я могу объединить логин и регистрацию в аутентификацию и получить 2 отдельных слоя. Это, вероятно, решит мою проблему. Но что произойдет, если, скажем, на странице профиля моего сайта я хочу показать им их ключ по какой-либо причине (возможно, я позволю htem изменить его или что-то в этом роде). А что мне теперь сломать СУХОЙ и написать то же самое? Или попробуйте создать репозиторий, который может как-то уместить все три эти таблицы с хорошим именем.   -  person chobo2    schedule 30.09.2009


Ответы (7)


Одна вещь, которую я сделал неправильно, когда играл с шаблоном репозитория - как и вы, я думал, что эта таблица относится к репозиторию 1: 1. Когда мы применяем некоторые правила из Domain Driven Design - проблема группировки репозиториев часто исчезает.

Репозиторий должен быть для совокупного корня, а не для таблицы. Это означает - если сущность не должна существовать одна (то есть - если у вас есть Registrant, который участвует в конкретном Registration) - это просто сущность, ей не нужен репозиторий, она должна обновляться / создаваться / извлекаться через репозиторий совокупности корень ему принадлежит.

Конечно - во многих случаях этот метод уменьшения количества репозиториев (на самом деле - это больше метод структурирования вашей модели домена) не может быть применен, потому что каждый объект должен быть совокупным корнем (что сильно зависит от вашего домена, Могу только догадываться вслепую). В вашем примере - License кажется совокупным корнем, потому что вам нужно иметь возможность проверять их без контекста объекта Registration.

Но это не ограничивает нас каскадными репозиториями (Registration репозиторий может ссылаться на License репозиторий, если это необходимо). Это не ограничивает нас ссылками на репозиторий License (предпочтительно - через IoC) непосредственно из объекта Registration.

Просто постарайтесь не доводить свой дизайн до усложнений, связанных с технологиями, или непонимания чего-либо. Группировать репозитории в ServiceX только потому, что вы не хотите создавать 2 репозитория, - не лучшая идея.

Гораздо лучше было бы дать ему собственное имя - RegistrationService, т.е.

Но в целом следует избегать сервисов - они часто становятся причиной, которая приводит к анемичной модели домена.

РЕДАКТИРОВАТЬ:
Начните использовать IoC. Это действительно облегчает внедрение зависимостей.
Вместо того, чтобы писать:

var registrationService = new RegistrationService(new RegistrationRepository(),  
      new LicenseRepository(), new GodOnlyKnowsWhatElseThatServiceNeeds());

вы сможете написать:

var registrationService = IoC.Resolve<IRegistrationService>();

P.s. Было бы лучше использовать так называемый общий локатор служб, но это всего лишь пример.

person Arnis Lapsa    schedule 13.10.2009
comment
Хахаха ... Я даю совет использовать локатор сервисов. Всегда приятно видеть, каким тупым я был в прошлом. - person Arnis Lapsa; 26.10.2010
comment
@Arnis Похоже, ваше мнение изменилось - мне интересно, как бы вы теперь по-другому ответили на этот вопрос? - person ngm; 14.04.2011
comment
@ngm многое изменилось с тех пор, как я ответил на это. Я все еще согласен с тем, что совокупный корень должен рисовать границы транзакций (сохраняться в целом), но я гораздо менее оптимистичен в отношении абстрагирования персистентности с использованием шаблона репозитория. В последнее время я просто использую ORM напрямую, потому что такие вещи, как управление нетерпеливой / ленивой загрузкой, слишком неудобны. Гораздо выгоднее сосредоточиться на разработке полнофункциональной модели предметной области, а не на абстрагировании настойчивости. - person Arnis Lapsa; 14.04.2011
comment
@Arnis - Спасибо за обновление, редко можно увидеть такое продолжение. - person Karl Nicoll; 15.04.2011
comment
@Arnis - Чтобы уточнить, вы рекомендуете вызывать ORM прямо из своих объектов? Итак, нет смысла игнорировать настойчивость ваших POCO ...? - person jpshook; 24.05.2011
comment
@ Разработчик: нет, не совсем так. они по-прежнему должны быть неосведомленными о настойчивости. вы извлекаете совокупный корень извне и вызываете на нем метод, который выполняет эту работу. в моей модели предметной области нет ссылок, только стандартные .net framework. для этого у вас должна быть богатая модель предметной области и достаточно умные инструменты (NHibernate делает свое дело). - person Arnis Lapsa; 24.05.2011
comment
Используя orm напрямую, вы переносите ответственность за постоянство (работа с отложенной загрузкой и т. Д.) И правильно сформированные запросы к коду, который использует OR / M. imho, который добавляет эти функции (нарушение SRP) к вашему методу, который хотел получить объекты. - person jgauffin; 20.05.2013
comment
@jgauffin каждый раз, когда вы получаете агрегат, вы находитесь в другом контексте. каждый раз, когда нужно загрузить что-то еще. Одна только забота о нетерпеливой загрузке говорит о том, что нет единой ответственности - это не просто получение агрегата. следовательно, попытка абстрагироваться от ORM может показаться притворным, что существует только один. и у большинства ORM уже есть интерфейс, который действует как список сущностей, который в основном является шаблоном репозитория. Уловка состоит в том, чтобы правильно провести черту - распознать потребности и варианты, а затем решить их с помощью самого простого возможного решения. - person Arnis Lapsa; 21.05.2013
comment
У вас слишком большие агрегаты, если вам нужно получить только их части. - person jgauffin; 21.05.2013
comment
@jgauffin У вас слишком много агрегатов, если вам нужно получить более одного из них - person Arnis Lapsa; 21.05.2013
comment
Напротив. Получение более одного ggregate часто означает, что вы можете использовать PK или индексы. Намного быстрее, чем использовать отношения, которые создает EF. Чем меньше корневые агрегаты, тем легче добиться от них производительности. - person jgauffin; 21.05.2013

Одна вещь, которую я начал делать, чтобы решить эту проблему, - это фактически разрабатывать сервисы, которые обертывают N-репозитории. Надеюсь, ваши фреймворки DI или IoC помогут упростить это.

public class ServiceImpl {
    public ServiceImpl(IRepo1 repo1, IRepo2 repo2...) { }
}

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

person neouser99    schedule 30.09.2009
comment
Нет, в этом нет особого смысла. В настоящее время я не использую фреймворки DI или IoC, потому что у меня и так достаточно возможностей. - person chobo2; 30.09.2009
comment
Если вы можете создать свои репозитории с помощью new (), вы можете попробовать это ... public ServiceImpl (): this (new Repo1, new Repo2 ...) {} в качестве дополнительного конструктора в службе. - person neouser99; 30.09.2009
comment
Внедрение зависимости? Я уже делаю это, но до сих пор не знаю, какой у вас код и что он решает. - person chobo2; 30.09.2009
comment
Я специально собираюсь после слияния репозиториев. Какой код не имеет смысла? Если вы используете DI, то код в ответе будет работать в том смысле, что ваша структура DI будет внедрять эти службы IRepo, в коде комментария это просто небольшая работа для выполнения DI (в основном ваш конструктор без параметров 'вводит' эти зависимости в ваш ServiceImpl). - person neouser99; 30.09.2009
comment
Я уже использую DI, поэтому могу лучше выполнять модульное тестирование. Моя проблема в том, что если вы сделаете свои сервисные слои и репозитории с подробными именами. Тогда, если вам когда-нибудь понадобится использовать их где-нибудь еще, вызов будет выглядеть странно, как уровень RegistrationService, который вызывает RegRepo в каком-то другом классе, например, ProfileClass. Поэтому я не вижу на вашем примере того, чем вы занимаетесь в полной мере. Например, если вы начнете иметь слишком много репозиториев на одном уровне обслуживания, у вас будет очень много другой бизнес-логики и логики проверки. Поскольку на уровне обслуживания вы обычно добавляете логику проверки. Так много, мне нужно больше ... - person chobo2; 01.10.2009
comment
Хорошо, я думаю, что теперь я понял, о чем вы говорите. На другом уровне обслуживания вы должны ввести другое Repo, а затем создать вокруг него метод-оболочку. Итак, если вы работаете на уровне ProfileService и вам нужно userName, вам не нужно вызывать RegService, вы просто вызываете RegRepo и имеете метод в своем ProfileService, например GetUserName {// call RegRepo.GetUser ()}. Так что это может сработать, но имя все равно сделано. Тем не менее, было бы странно, если бы вы создавали объект регистрации, скажем, на уровне ProfileService. На мой взгляд, самое сложное в шаблоне репоистории - это именование ... - person chobo2; 01.10.2009
comment
-1 = ›создание сервисов только для групповых репозиториев - это совсем не правильно. - person Arnis Lapsa; 13.10.2009

Что я делаю, так это то, что у меня есть абстрактный базовый класс, определенный следующим образом:

public abstract class ReadOnlyRepository<T,V>
{
     V Find(T lookupKey);
}

public abstract class InsertRepository<T>
{
     void Add(T entityToSave);
}

public abstract class UpdateRepository<T,V>
{
     V Update(T entityToUpdate);
}

public abstract class DeleteRepository<T>
{
     void Delete(T entityToDelete);
}

Затем вы можете получить свой репозиторий из абстрактного базового класса и расширить свой единственный репозиторий, если, например, общие аргументы отличаются;

public class RegistrationRepository: ReadOnlyRepository<int, IRegistrationItem>,
                                     ReadOnlyRepository<string, IRegistrationItem> 

так далее....

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

person Michael Mann    schedule 30.09.2009
comment
Итак, вы пытаетесь создать общий репозиторий для всего этого? - person chobo2; 30.09.2009
comment
Итак, что на самом деле входит, скажем, метод обновления. Как будто у вас есть подобное V-обновление, и оно передается в T entitytoUpdate, но нет кода, действительно обновляющего его, или он есть? - person chobo2; 30.09.2009
comment
Да ... В методе обновления будет код, потому что вы напишете класс, который происходит от общего репозитория. Код реализации может быть Linq2SQL или ADO.NET или любым другим, выбранным вами в качестве технологии реализации доступа к данным. - person Michael Mann; 01.10.2009

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MvcRepository
{
    public class Repository<T> : IRepository<T> where T : class
    {
        protected System.Data.Linq.DataContext _dataContextFactory;

        public IQueryable<T> All()
        {
            return GetTable.AsQueryable();
        }

        public IQueryable<T> FindAll(Func<T, bool> exp)
        {
            return GetTable.Where<T>(exp).AsQueryable();
        }

        public T Single(Func<T, bool> exp)
        {
            return GetTable.Single(exp);
        }

        public virtual void MarkForDeletion(T entity)
        {
            _dataContextFactory.GetTable<T>().DeleteOnSubmit(entity);
        }

        public virtual T CreateInstance()
        {
            T entity = Activator.CreateInstance<T>();
            GetTable.InsertOnSubmit(entity);
            return entity;
        }

        public void SaveAll()
        {
            _dataContextFactory.SubmitChanges();
        }

        public Repository(System.Data.Linq.DataContext dataContextFactory)
        {
            _dataContextFactory = dataContextFactory;
        }

        public System.Data.Linq.Table<T> GetTable
        {
            get { return _dataContextFactory.GetTable<T>(); }
        }

    }
}

ИЗМЕНИТЬ

public class AdminRepository<T> : Repository<T> where T: class
{
    static AdminDataContext dc = new AdminDataContext(System.Configuration.ConfigurationManager.ConnectionStrings["MY_ConnectionString"].ConnectionString);

    public AdminRepository()
        : base( dc )
    {
    }

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

Итак, теперь у меня есть стандартный репозиторий, реализующий стандартные вызовы, такие как All и Find, а в моем AdminRepository есть определенные вызовы.

Не отвечает на вопрос о СУХОМ, хотя не думаю.

person griegs    schedule 30.09.2009
comment
Для чего нужен репозиторий? а как CreateInstance? Не уверен, что делает все, что у вас есть. - person chobo2; 30.09.2009
comment
Это общее, как и все. По сути, вам нужно иметь конкретный репозиторий для вашей (области). Ознакомьтесь с приведенным выше изменением для моего AdminRepository. - person griegs; 30.09.2009

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

person James Jones    schedule 18.02.2010

Я предлагаю вам взглянуть на Sharp Architecture. Они предлагают использовать один репозиторий для каждой сущности. В настоящее время я использую его в своем проекте и очень доволен результатами.

person Sly    schedule 30.09.2009
comment
Я бы дал здесь +1, но он указал, что контейнеры DI или IoC не совсем подходят (если это не единственные преимущества Sharp Arch). Я предполагаю, что есть часть существующего кода, над которым он работает. - person neouser99; 30.09.2009
comment
Что считается юридическим лицом? Это вся база данных? или это таблица базы данных? Контейнеры DI или IoC не подходят в настоящее время, так как я просто не хочу изучать это поверх других 10 вещей, которые я изучаю одновременно. они то, что я изучу в моей следующей версии моего сайта или в моем следующем проекте. Хотя я не уверен, что это будет быстрый просмотр сайта, и мне кажется, что вы хотите, чтобы вы использовали nhirbrate, а я сейчас использую linq to sql. - person chobo2; 30.09.2009

Шаблон репозитория - плохой шаблон проектирования. Я работаю со многими старыми проектами .Net, и этот шаблон обычно вызывает ошибки «Распределенные транзакции», «Частичный откат» и «Исчерпание пула подключений», которых можно было избежать. Проблема в том, что шаблон пытается обрабатывать соединения и транзакции изнутри, но они должны обрабатываться на уровне контроллера. Также EntityFramework уже абстрагирует большую часть логики. Я бы предложил использовать шаблон Service вместо повторного использования общего кода.

person ColacX    schedule 07.01.2016