Як правильно використовувати шаблон сховища?


86

Мені цікаво, як слід групувати свої сховища? Як і на прикладах, які я бачив на asp.net mvc, і в моїх книгах вони в основному використовують одне сховище для кожної таблиці бази даних. Але, схоже, багато сховищ змушують вас викликати багато сховищ пізніше для знущань та іншого.

Тож я здогадуюсь, що я мав би їх згрупувати. Однак я не знаю, як їх згрупувати.

Зараз я створив сховище реєстрацій, щоб обробляти всі мої реєстраційні матеріали. Однак є приблизно 4 таблиці, які мені потрібно оновити, і раніше у мене було 3 сховища, щоб зробити це.

Наприклад, одна з таблиць - це таблиця ліцензій. Коли вони реєструються, я дивлюсь на їхній ключ і перевіряю його, чи існує в базі даних. Тепер, що трапиться, якщо мені потрібно перевірити цей ліцензійний ключ або щось інше з цієї таблиці в іншому місці, крім реєстрації?

Одне місце може бути логіном (перевірте, чи не закінчився термін дії ключа).

То що б я зробив у цій ситуації? Переписати код ще раз (зламати СУХИЙ)? Спробуйте об'єднати ці 2 сховища разом і сподівайтесь, що жоден із методів не потрібен в якийсь інший момент часу (наприклад, можливо, у мене може бути метод, який перевіряє, чи використовується userName - можливо, мені це знадобиться десь ще).

Крім того, якщо я об'єднаю їх разом, мені знадобляться або два сервісних шари, що переходять до одного сховища, оскільки я думаю, що наявність усієї логіки для 2 різних частин сайту буде тривалим, і я повинен мати такі імена, як ValidateLogin (), ValdiateRegistrationForm () , ValdiateLoginRetrievePassword () та ін.

Або в будь-якому випадку зателефонувати до сховища і просто мати навколо дивне звукове ім’я?

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


11
+1. Чудове запитання.
griegs

Дякую, це вже деякий час мене глючить. Оскільки я вважаю, що побачені в книзі занадто прості, вони не показують вам, що робити в таких ситуаціях.
chobo2

Я в основному роблю те саме, що і ви, так, якщо у мене є клас linq2sql, який використовується в більш ніж 1 сховищі, і мені потрібно змінити структуру таблиці, яку я розбиваю DRY. Менше, ніж ідеально. Зараз я планую це трохи краще, тому мені не потрібно використовувати клас linq2sql більше одного разу, що, на мою думку, є добрим розділенням проблем, але я передбачаю день, коли це стане для мене справжньою проблемою.
griegs

Я задав таке запитання (але не ідентичний) тут: stackoverflow.com/questions/910156 / ...
Grokys

Так, але, здається, важко спланувати це для всіх ситуацій. Як я вже сказав, я можу об'єднати логін та реєстрацію в Аутентифікацію та мати два окремих шари. Це, мабуть, вирішить мою проблему. Але що трапиться, якщо, скажімо, на сторінці профілю мого сайту я з будь-якої причини хочу показати їм їх ключ (можливо, я дозволю htem це змінити або щось інше). А тепер, що я розбиваю СУХО і пишу те саме? Або спробуйте створити сховище, яке якимось чином вмістить усі 3 ці таблиці з гарним ім’ям.
chobo2

Відповіді:


41

Одне, що я зробив неправильно, коли погрався з шаблоном сховища - так само, як і ви, я думав, що таблиця відноситься до сховища 1: 1. Коли ми застосовуємо деякі правила з доменного дизайну - проблема згрупування сховищ часто зникає.

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

Звичайно - у багатьох випадках цю техніку зменшення кількості сховищ (насправді - це більше техніка структурування вашої доменної моделі) не можна застосувати, оскільки кожна сутність повинна бути сукупним коренем (що сильно залежить від вашого домену, Я можу надати лише сліпі здогади). У вашому прикладі - Licenseздається сукупним коренем, тому що ви повинні мати можливість перевіряти їх без контексту Registrationсутності.

Але це не обмежує нас каскадними сховищами (при необхідності Registrationсховище може посилатися на Licenseсховище). Це не обмежує нас посиланням на Licenseсховище (бажано - через IoC) безпосередньо з Registrationоб'єкта.

Просто намагайтеся не керувати своїм дизайном через ускладнення, спричинені технологіями, або щось нерозуміння. Групування сховищ ServiceXлише тому, що ви не хочете створювати 2 сховища, це не гарна ідея.

Набагато краще було б дати йому власне ім'я - RegistrationServiceтобто

Але послуг слід взагалі уникати - вони часто є причиною, яка веде до анемічної моделі домену .

EDIT:
Почніть використовувати 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
@Developer ні, не зовсім. вони все одно повинні бути наполегливими неуками. ви отримуєте сукупний корінь ззовні і викликаєте на ньому метод, який виконує цю роботу. моя модель домену має нульові посилання, лише стандартні .net framework. щоб досягти цього, Ви повинні мати багату модель домену та інструменти, які є досить розумними (NHibernate робить свою справу).
Арніс Лапса,

1
Навпаки. Отримання декількох збірних часто означає, що ви можете використовувати PK або індекси. Набагато швидше, ніж скористатися перевагами відносин, які породжує EF. Чим менші кореневі агрегати, тим легше отримати на них продуктивність.
jgauffin

5

Одне, що я почав робити для вирішення цього, - це насправді розробляти служби, які обертають N сховищ. Сподіваємось, ваші рамки DI або IoC можуть допомогти зробити це простіше.

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

Чи має це сенс? Крім того, я розумію, що, говорячи про послуги в цій садибі, може, а може і не відповідати принципам DDD, я просто роблю це, тому що це, здається, працює.


1
Але це не має великого сенсу. На даний момент я не використовую DI або IoC фреймворки, тому що мені достатньо, як є.
chobo2

1
Якщо ви можете створити екземпляр своїх репозиторіїв лише з новим (), ви можете спробувати цей ... public ServiceImpl (): this (новий Repo1, новий Repo2 ...) {} як додатковий конструктор у службі.
neouser99

Ін'єкція залежності? Я це вже роблю, але досі не впевнений, що ваш код dong і що він вирішує.
chobo2

Я спеціально збираюся після об’єднання сховищ. Який код не має сенсу? Якщо ви використовуєте DI, тоді код у відповіді буде працювати в тому сенсі, що ваш фреймворк DI буде вводити ці служби IRepo, в коді коментаря це в основному невелика робота навколо DI (в основному ваш конструктор параметрів не "вводить" ці залежності до вашого ServiceImpl).
neouser99

Я вже використовую DI, щоб я міг краще тестувати модуль. Моя проблема полягає в тому, що якщо ви робите свої сервісні рівні та репозиторії з детальною інформацією про імена. Тоді, якщо вам коли-небудь знадобиться використовувати їх де-небудь ще, це буде виглядати дивним викликом, як рівень RegistrationService, який викликає RegRepo в іншому класі, наприклад, як ProfileClass. Тому я не бачу на вашому прикладі, що ви повністю робите. Подобається, якщо у вас починає бути занадто багато репозиторіїв на одному сервісному рівні, у вас буде стільки різної логіки бізнесу та логіки перевірки. Оскільки на сервісному рівні ви зазвичай вводите логіку перевірки. Так багато мені потрібно більше ...
chobo2

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

У мене це клас сховища, і я розширюю його в сховищі таблиць / областей, але все одно мені іноді доводиться переривати СУХУ.

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 and Find, і в моєму AdminRepository я маю конкретні виклики.

Не відповідає на питання СУХОГО, хоча я не думаю.


Для чого потрібне "Сховище"? і подобається CreateInstance? Не впевнений, чим займається все у вас
chobo2

Це загальне як будь-що. В основному вам потрібно мати конкретне сховище для вашого (району). Перевірте редагування вище для мого AdminRepository.
griegs

1

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


1

Шаблон сховища - це поганий шаблон дизайну. Я працюю з багатьма старими проектами .Net, і цей шаблон зазвичай спричиняє помилки "Розподілені транзакції", "Частковий відкат" та "Вичерпаний пул з'єднань", яких можна уникнути. Проблема полягає в тому, що шаблон намагається обробляти з'єднання та транзакції внутрішньо, але вони повинні оброблятися на рівні контролера. Також EntityFramework вже абстрагує багато логіки. Я б запропонував використовувати шаблон Service замість цього для повторного використання спільного коду.


0

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


Я хотів би дати +1 тут, але він зазначив, що DI або IoC контейнери не є цілком можливим варіантом (за умови, що це не єдина перевага Sharp Arch). Я припускаю, що існує багато до багатьох існуючих кодів, над якими він працює.
neouser99

Що вважається суб'єктом господарювання? Це вся база даних? чи це таблиця бази даних? Наразі контейнери DI або IoC не є можливістю, оскільки я просто не хочу навчитися цього на тлі інших 10 речей, які я вивчаю одночасно. це те, що я розгляну в наступному перегляді свого сайту або в наступному проекті. Незважаючи на те, що я не впевнений, чи це буде цей швидкий перегляд сайту, і, схоже, хочеться, щоб ви використовували nhirbrate, і я використовую для посилання на sql на даний момент.
chobo2
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.