Entity Framework Entities - Деякі дані веб-сервісу - найкраща архітектура?


10

В даний час ми використовуємо Entity Framework в якості ОРМ для кількох веб-додатків, і до цього часу він нам добре підходить, оскільки всі наші дані зберігаються в одній базі даних. Ми використовуємо шаблон репозиторію та маємо служби (доменний рівень), які використовують їх, і повертають об'єкти EF безпосередньо до контролерів ASP.NET MVC.

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

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

Моя початкова думка полягала лише в тому, щоб створити модельний клас обгортки, який містив би об'єкт EF (EFUser), і новий клас "ApiUser", що містить нову інформацію - і коли ми отримуємо користувача, ми отримуємо EFUser, а потім отримуємо додатковий інформація від API та заповнити об'єкт ApiUser. Однак, хоча це буде добре для отримання одиноких користувачів, це стає причиною отримання кількох користувачів. Ми не можемо потрапити на API при отриманні списку користувачів.

Друга моя думка полягала в тому, щоб додати єдиний метод до об'єкта EFUser, який повертає ApiUser, і просто заповнювати його, коли це потрібно. Це вирішує вказану вище проблему, оскільки ми отримуємо доступ до неї лише тоді, коли нам це потрібно.

Або остання думка полягала в тому, щоб зберегти локальну копію даних у нашій базі даних та синхронізувати її з API, коли користувач увійде в систему. Це мінімальна робота, оскільки це просто процес синхронізації - і ми не маємо накладних витрат. DB та API щоразу, коли ми хочемо отримати інформацію про користувача. Однак це означає зберігання даних у двох місцях, а також означає, що дані застаріли для будь-якого користувача, який не входив протягом певного часу.

Хтось має поради чи пропозиції щодо того, як найкраще впоратися з таким сценарієм?


it's not really sensible to fetch it on an ad-hoc basis- Чому? З причин ефективності?
Роберт Харві

Я не маю на увазі потрапляння API на спеціальний принцип - я просто маю на увазі збереження існуючої структури об'єкта таким, яким він є, а потім при необхідності викликати ad-hoc API у веб-програмі - я просто мав на увазі, що цього не буде Розумно, як це потрібно зробити в багатьох місцях.
stevehayter

Відповіді:


3

Ваша справа

У вашому випадку всі три варіанти є життєздатними. Я думаю, що найкращим варіантом є, мабуть, синхронізація джерел даних десь, про яку програма asp.net навіть не знає. Тобто уникайте двох вибірок на передньому плані щоразу, синхронізуйте API з db мовчки). Тож якщо у вашому випадку це життєздатний варіант - я кажу, зробіть це.

Рішення, коли ви робите вибір "один раз", як пропонує інша відповідь, не здається дуже життєздатним, оскільки він не зберігається у відповідь ніде, і ASP.NET MVC просто зробить отримання для кожного запиту знову і знову.

Я б уникнув одиночного, я не думаю, що це взагалі гарна ідея з багатьох звичних причин.

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

Я думаю, це дійсно зводиться до кількох питань:

  • Як часто змінюються дані дзвінків API? Не часто? Третій варіант. Часто? Раптом третій варіант не надто життєздатний. Я не впевнений, що я проти спеціальних дзвінків, як і ви.
  • Наскільки дорогий дзвінок API? Ви платите за дзвінок? Швидкі вони? Безкоштовно? Якщо вони швидкі, щоразу можуть зателефонувати, якщо вони повільні, вам потрібно мати якийсь прогноз на місці та робити дзвінки. Якщо вони коштують грошей - це великий стимул для кешування.
  • Наскільки швидким повинен бути час відповіді? Очевидно, що швидше краще, але жертвувати швидкістю для простоти в деяких випадках варто, особливо якщо це не безпосередньо стоїть перед користувачем.
  • Наскільки відрізняються дані API від ваших даних? Вони дві концептуально різні речі? Якщо це так, може бути навіть краще просто виставити API зовні, а не повернути результат API безпосередньо з результатом і дозволити іншій стороні здійснити другий виклик і керувати ним управлінням.

Слово-два про розділення проблем

Дозвольте мені заперечувати проти того, що говорить Бобсон про розділення проблем. Наприкінці дня - введення такої логіки в такі сутності порушує поділ проблем так само погано.

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

У цьому пов'язаному питанні я запитав про доступ до об'єктів безпосередньо від контролера. Дозвольте мені навести одну з відповідей там:

"Ласкаво просимо до BigPizza, спеціальної магазини піци, чи можу я взяти ваше замовлення?" "Ну, я хотів би піцу з оливками, але томатний соус зверху і сир внизу і запікати його в духовці протягом 90 хвилин, поки він не стане чорним і твердим, як плоска гранітна скеля". "Добре, сер, спеціальні піци - це наша професія, ми це зробимо".

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

"Ні!", Кричить кухар, "не знову! Ви знаєте, ми це вже пробували". Він бере стос паперу на 400 сторінок, "тут у нас граніт із 2005 року, але ... у нього не було оливок, а натомість паприка ... або ось верхній помідор ... але клієнт цього хотів запікали лише півхвилини ». "Може бути, ми повинні назвати це TopTomatoGraniteRockSpecial?" "Але він не враховує сиру внизу ..." Касир: "Це те, що повинно виражати Спеціальне". "Але створити скелю Піцу як піраміду також було б особливим", - відповідає кухар. "Гммм ... важко ...", - відчайдушно каже касир.

"МОЙ ПІЦА ВЖЕ В ПІСНІ?", Раптом він кричить через кухонні двері. "Давайте припинимо цю дискусію, просто скажіть мені, як зробити цю Піцу. Ми не збираємося вдруге такої Піци", - вирішує кухар. "Гаразд, це Піца з оливками, але зверху томатний соус і сир внизу, і запікайте його в духовці протягом 90 хвилин, поки він не стане чорним і твердим, як плоска гранітна скеля".

(Прочитайте решту відповіді, це справді приємно imo).

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


Дякуємо за вашу дуже ретельну відповідь @Benjamin. Я спочатку почав створювати прототип, використовуючи рішення Бобсона вище (ще до того, як він опублікував свою відповідь), але ви піднімаєте деякі важливі моменти. Щоб відповісти на ваші запитання: - Дзвінок API не дуже дорогий (вони безкоштовні, а також швидкі). - Деякі частини даних будуть змінюватися досить регулярно (деякі навіть кожні пару годин). - Швидкість досить важлива, але аудиторія програми така, що полегшення швидкого завантаження не є абсолютною вимогою.
stevehayter

@stevehayter У такому випадку я, швидше за все, здійснюватиму дзвінки в API від клієнта. Це дешевше і швидше, і це дає вам тонший зернистий контроль.
Бенджамін Груенбаум

1
На основі цих відповідей я менше схиляюся до збереження локальної копії даних. Я насправді схиляюся до викриття API окремо і таким чином обробляє додаткові дані. Я думаю, що це може бути хорошим компромісом між простотою рішення @ Бобсона, але також додає ступеня розлуки, що мені трохи зручніше. Я перегляну цю стратегію в своєму прототипі і звітую про свої висновки (і нагороду отримаю!).
stevehayter

@BenjaminGruenbaum - я не впевнений, що я дотримуюся твого аргументу. Як моя пропозиція робить репозиторій обізнаним про презентацію? Звичайно, відомо про те, що доступ до поля, підтримуваного API, був доступний, але це не має нічого спільного з тим, що робить представлення з цією інформацією.
Бобсон

1
Я вирішив перемістити все на сторону клієнта - але як метод розширення на EFUser (який існує в шарі презентації, в окремій збірці). Метод просто повертає дані з API і встановлює сингтон, щоб він не потрапляв повторно. Термін експлуатації об’єктів настільки короткий, що я не маю жодних проблем з використанням одинарної кнопки. Таким чином, є певна ступінь відокремленості, але я все одно отримую зручність працювати з суб’єктом EFUser. Дякую всім респондентам за допомогу. Однозначно цікава дискусія :).
stevehayter

2

При належному розділенні проблем , нічого вище рівня Entity Framework / API навіть не повинно розуміти, звідки беруться дані. Якщо виклик API не є дорогим (за часом або обробкою), доступ до даних, які використовують його, повинен бути таким же прозорим, як і доступ до даних із бази даних.

Оптимальним способом цього реалізувати було б додати додаткові властивості до EFUserоб’єкта, який ледаче завантажує дані API за потребою. Щось на зразок цього:

partial class EFUser
{
    private APIUser _apiUser;
    private APIUser ApiUser
    {
       get { 
          if (_apiUser == null) _apiUser = API.LoadUser(this.ExternalID);
          return _apiUser;
       }
    }
    public string CompanyName { get { return ApiUser.UserCompanyName; } }
    public string JobTitle{ get { return ApiUser.UserJobTitle; } }
}

Зовні, коли вперше CompanyNameабо JobTitleвикористовується, буде зроблений єдиний виклик API (і, таким чином, невелика затримка), але всі наступні дзвінки, поки об'єкт не буде знищений, будуть настільки ж швидкими та простими, як і доступ до бази даних.


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

0

Одна з ідей - змінити ApiUser так, щоб він не завжди мав зайву інформацію. Замість цього ви ставите метод ApiUser для його отримання:

ApiUser apiUser = backend.load($id);
//Locally available data directly on the ApiUser like so:
String name = apiUser.getName();
//Expensive extra data available after extra call:
UserExtraData extraData = apiUser.fetchExtraData();
String managerName = extraData.getManagerName();

Ви також можете трохи змінити це, щоб використовувати ледаче завантаження додаткових даних, так що вам не доведеться витягувати UserExtraData з об'єкта ApiUser:

//Extra data fetched on first get:
String managerName = apiUser.lazyGetExtraData().getManagerName();

Таким чином, коли у вас є список, додаткові дані не будуть отримані за замовчуванням. Але ви все одно можете отримати доступ до нього під час проходження списку!


Не дуже впевнений, що ви тут маєте на увазі - у backend.load () ми вже робимо навантаження - так що напевно це завантажило б дані API?
stevehayter

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