Створення шару абстракції над шаром ORM


12

Я вважаю, що якщо у вас є ваші сховища, використовуйте ORM, що це вже досить абстраговано з бази даних.

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

Це дійсно потрібно чи просто багато над головою, щоб створити шар, який буде працювати на багатьох ORM?

Редагувати

Просто, щоб детальніше:

  1. У нас є клас POCO та Entity Class, які відображені за допомогою AutoMapper. Клас сутності використовується шаром репозиторію. Потім рівень репозиторію використовує додатковий шар абстракції для зв'язку з Entity Framework.
  2. Діловий рівень жодним чином не має прямого доступу до Entity Framework. Навіть без додаткового шару абстрагування над ORM, для цього потрібно використовувати сервісний рівень, який користується шаром сховища. В обох випадках бізнес-рівень повністю відокремлений від ORM.
  3. Основним аргументом є можливість зміни ORM у майбутньому. Оскільки він дійсно локалізований всередині шару репозиторію, для мене він уже добре відокремлений, і я не бачу, чому потрібен додатковий шар абстракції, щоб мати код "якості".

3
додатковий шар потребує виправдання, інакше він порушує YAGNI. Іншими словами, хтось , хто вірить, що вам це потрібно, має тягар довести це
гнат

2
Я можу зрозуміти, що хочеться доменного шару, який виявляє лише потрібний підмножина операцій - ORM, як правило, занадто широка площа поверхні (скажімо, ви не хочете дозволити оновлення об'єкту, не керованого іншим об'єктом, що містить). Наявність такого шару абстракції допомагає в цьому.
Одід

4
Можливо, вам знадобиться другий шар абстракції для першого шару абстракції над ОРМ на випадок, якщо ви хочете змінити і перший шар.
Девід Пітерман

1
@David Поки ми додаємо зменшення кількості, змініть усі свої if (boolean) на if (boolean == true), і якщо ви хочете відрегулювати більше того ж, якщо (boolean == true == true ...) і так далі
Брайан

1
Цікавий пов’язаний пост: ayende.com/blog/3955/repository-is-the-new-singleton
RMalke

Відповіді:


12

Таким чином лежить божевілля. Навряд чи вам коли-небудь знадобиться міняти ORM. І якщо ви коли-небудь вирішите змінити ORM, вартість переписування карт буде незначною часткою витрат на розробку та підтримку власного мета-ORM. Я би сподівався, що ви можете написати кілька сценаріїв, щоб виконати 95% роботи, необхідної для перемикання ORM.

Власні рамки майже завжди є катастрофою. Побудова такої в очікуванні майбутніх потреб - майже гарантована катастрофа. Успішні рамки витягнуті з успішних проектів, а не побудовані достроково для задоволення уявних потреб.


12

ORM надає абстракцію для того, щоб ваш рівень даних був незалежним від RDBMS, але його може бути недостатньо, щоб "відв'язати" ваш бізнес-рівень від вашого рівня даних. Зокрема, ви не повинні дозволяти об'єктам, які відображають таблиці RDBMS, "просочуватися" безпосередньо в бізнес-рівень.

Принаймні, ваш бізнес-рівень повинен запрограмувати інтерфейси, які потенційно можуть реалізувати ваші керовані ORM об’єкти, нанесені на таблицю із рівня даних. Крім того, вам може знадобитися створити на основі інтерфейсу шар абстрактної побудови запитів, щоб приховати власні можливості запиту вашої ORM. Основна мета - уникнути "введення" будь-якого конкретного ОРМ у ваше рішення за межі його рівня даних. Наприклад, може виникнути спокуса створити рядки HQL ( Hibernate Query Language ) у бізнес-шарі. Однак це, здавалося б, невинне рішення пов'язувало б ваш бізнес-шар із сплячим режимом, тим самим з’єднуючи бізнес та шари доступу до даних разом; ви повинні намагатися максимально уникати цієї ситуації.

РЕДАКТУВАННЯ: У вашому випадку додатковий шар всередині сховища - це марна трата часу: виходячи з другого пункту, ваш бізнес-шар достатньо ізольований від вашого сховища. Забезпечення додаткової теплоізоляції призведе до зайвої складності, без додаткових переваг.

Проблема зі створенням додаткового шару абстракції всередині вашого сховища полягає в тому, що конкретна «марка» ORM диктує спосіб взаємодії з ним. Якщо ви побудуєте тонку обгортку, схожу на ваш ORM, але під вашим контролем, заміна базового ORM буде приблизно такою ж важкою, як це було б без цього додаткового шару. Якщо, з іншого боку, ви будуєте шар, який не схожий на ваш ORM, тоді ви повинні поставити під сумнів свій вибір технології об'єктно-реляційного відображення.


Я дуже радий. NET вирішив цю проблему, вивівши об’єкт запитів на платформу. Порт .NET Hibernate навіть підтримує його.
Майкл Браун

2
@MikeBrown Так, і .NET також постачав дві власні конкуруючі технології ORM, обидві за допомогою технології LINQ!
dasblinkenlight

@dasblinkenlight Я оновив питання, щоб надати додаткову інформацію.
Патрік Дежардінс

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

2

UnitOfWork зазвичай забезпечує цю абстракцію. Це одне місце, яке потрібно змінити, ваші сховища залежать від нього за допомогою інтерфейсу. Якщо вам коли-небудь потрібно змінити O / RM, просто впровадіть новий UoW над ним. Один і готовий.

До речі, це виходить за рамки просто комутації O / RM, подумайте про тестування одиниць. У мене є три реалізації UnitOfWork, одна для EF, одна для NH (тому що я фактично DID повинен перемикати середній проект O / RM для клієнта, який хотів підтримати Oracle), і один для збереження InMemory. Наполегливість InMemory була ідеальною для тестування одиниць або навіть для швидкого прототипування, перш ніж я був готовий поставити за нею базу даних.

Рамка проста у виконанні. Спочатку у вас є загальний інтерфейс IRepository

public interface IRepository<T>
  where T:class
{
  IQueryable<T> FindBy(Expression<Func<T,Bool>>filter);
  IQueryable<T> GetAll();
  T FindSingle(Expression<Func<T,Bool>> filter);
  void Add(T item);
  void Remove(T item);

}

І інтерфейс IUnitOfWork

public interface IUnitOfWork
{
   IQueryable<T> GetSet<T>();
   void Save();
   void Add<T>(T item) where T:class;
   void Remove<T>(T item) where T:class;
}

Далі - базове сховище (ваш вибір щодо того, чи повинен він бути абстрактним чи ні)

public abstract class RepositoryBase<T>:IRepository<T>
  where T:class
{
   protected readonly IUnitOfWork _uow;

   protected RepositoryBase(IUnitOfWork uow)
   { 
      _uow=uow;
   }

   public IQueryable<T> FindBy(Expression<Func<T,Bool>>filter)
   {
      return _uow.GetSet<T>().Where(filter);
   }

   public IQueryable<T> GetAll()
   {
      return _uow.GetSet<T>();
   }

   public T FindSingle(Expression<Func<T,Bool>> filter)
   {
      return _uow.GetSet<T>().SingleOrDefault(filter);
   }

   public void Add(T item)
   {
      return _uow.Add(item);
   }

   public void Remove(T item)
   {
      return _uow.Remove(item);
   }
}

Реалізація IUnitOfWork - це дитяча гра для EF, NH та In Memory. Причина, по якій я повертаю IQueryable, полягає в тій же самій причині, що Аєнде згадував у своєму дописі, клієнт може додатково фільтрувати, сортувати, групувати та навіть проектувати результат за допомогою LINQ, і ви все одно отримуєте користь від того, що все це робиться на стороні сервера.


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

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

Надання шару імені не означає, що це потрібно чи корисно.
кевін клайн

Зауважте, згідно з ОП, він має додаткове відображення між доступом до даних та бізнес-рівнем. Для мене об’єкти бізнесу та об’єкти юридичної особи однакові. EF та NH надають дивовижні API для відображення даних, так що відображення даних рідко (якщо взагалі колись) стає проблемою.
Майкл Браун

Як перевести довільний вираз на ефективний виклик ORM? Ви не можете просто взяти все та викинути рядки, які не відповідають фільтру.
Кевін Клайн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.