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

86

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

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

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

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

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

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

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

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

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

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

Ответы:

41

Одна вещь, которую я сделал неправильно, когда играл с шаблоном репозитория - как и вы, я думал, что эта таблица относится к репозиторию 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>();

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

Арнис Лапса
источник
17
Ха-ха-ха ... Даю совет использовать локатор сервисов. Всегда приятно видеть, каким тупым я был в прошлом.
Арнис Лапса,
1
@Arnis Похоже, ваше мнение изменилось - мне интересно, как бы вы теперь по-другому ответили на этот вопрос?
ngm
10
@ngm многое изменилось с тех пор, как я ответил на это. Я все еще согласен с тем, что совокупный корень должен рисовать границы транзакций (сохраняться как единое целое), но я гораздо менее оптимистичен в отношении абстрагирования персистентности с использованием шаблона репозитория. В последнее время я просто использую ORM напрямую, потому что такие вещи, как управление нетерпеливой / ленивой загрузкой, слишком неудобны. Гораздо выгоднее сосредоточиться на разработке полнофункциональной модели предметной области, а не на абстрагировании устойчивости.
Арнис Лапса,
2
@ Разработчик: нет, не совсем так. они все еще должны быть неосведомленными о настойчивости. вы извлекаете совокупный корень извне и вызываете на нем метод, который выполняет эту работу. в моей модели предметной области нет ссылок, только стандартные .net framework. для этого у вас должна быть богатая модель предметной области и достаточно умные инструменты (NHibernate делает свое дело).
Арнис Лапса
1
Наоборот. Получение более одного ggregate часто означает, что вы можете использовать PK или индексы. Намного быстрее, чем использовать отношения, которые создает EF. Чем меньше корневые агрегаты, тем легче добиться от них производительности.
jgauffin
5

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

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

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

neouser99
источник
1
Нет, в этом нет особого смысла. Я не использую фреймворки DI или IoC в настоящее время, потому что у меня и так достаточно возможностей.
chobo2,
1
Если вы можете создать свои репозитории с помощью new (), вы можете попробовать это ... public ServiceImpl (): this (new Repo1, new Repo2 ...) {} в качестве дополнительного конструктора в службе.
neouser99
Внедрение зависимости? Я уже делаю это, но до сих пор не уверен, что это за код и что он решает.
chobo2,
Я специально собираюсь после слияния репозиториев. Какой код не имеет смысла? Если вы используете DI, то код в ответе будет работать в том смысле, что ваша инфраструктура DI будет внедрять эти службы IRepo, в коде комментария это просто небольшая работа для выполнения DI (в основном ваш конструктор без параметров 'вводит' эти зависимости в ваш ServiceImpl).
neouser99
Я уже использую DI, поэтому могу лучше выполнять модульное тестирование. Моя проблема в том, что если вы сделаете свои сервисные слои и репозитории с подробными именами. Тогда, если вам когда-нибудь понадобится использовать их где-нибудь еще, вызов будет выглядеть странно, как уровень RegistrationService, который вызывает RegRepo в каком-то другом классе, например, ProfileClass. Поэтому я не вижу на вашем примере того, чем вы занимаетесь в полной мере. Например, если вы начнете иметь слишком много репозиториев на одном уровне обслуживания, у вас будет очень много другой бизнес-логики и логики проверки. Поскольку на уровне обслуживания вы обычно добавляете логику проверки. Так много, мне нужно больше ...
chobo2 01
2

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

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> 

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

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

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

У меня это как класс репозитория, и да, я расширяю репозиторий таблицы / области, но все же мне иногда приходится ломать 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 есть определенные вызовы.

Не отвечает на вопрос о DRY, хотя не думаю.

григи
источник
Для чего "Репозиторий"? а как CreateInstance? Не уверен, что делает все, что у вас есть.
chobo2,
Это общее как угодно. По сути, вам нужно иметь определенный репозиторий для вашего (региона). Проверьте правку выше для моего AdminRepository.
griegs
1

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

Джеймс Джонс
источник
1

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

ColacX
источник
0

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

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