чи погана практика, що контролер викликає сховище замість служби?


43

чи погана практика, що контролер викликає сховище замість служби?

щоб пояснити більше:

Я розумію, що в хорошому дизайні контролери викликають сервіс і сервіс використовують сховище.

але іноді в контролері я не маю / не потребую ніякої логіки, і мені просто потрібно взяти з db і передати його для перегляду.

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


Як ви телефонуєте в службу? Через інтерфейс REST?
Роберт Харві

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

3
Мех. Якщо це невелика програма, і ви просто намагаєтеся отримати дані з бази даних, сервісний рівень - це марна трата часу, якщо цей службовий рівень не є частиною публічного API, наприклад інтерфейсу REST. "Молоко добре для вас чи погано для вас?" Залежить від того, чи не викликаєш ти лактозу.
Роберт Харві

4
Не існує жорсткого і швидкого правила, згідно з яким у вас повинен бути контролер -> сервіс -> структура репозиторію через контролер -> сховище. Виберіть правильну схему для правильного застосування. Я б сказав, що ви повинні зробити свою заяву послідовною.
NikolaiDante

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

Відповіді:


32

Ні, подумайте про це так: сховище - це послуга (також).

Якщо суб'єкти, які ви отримуєте через сховище, обробляють більшу частину бізнес-логіки, в інших службах немає потреби. Досить мати сховище.

Навіть якщо у вас є деякі послуги, які ви повинні пройти, щоб маніпулювати своїми сутностями. Спершу візьміть об’єкт із сховища, а потім передайте його вказаній службі. Вміти підкидати HTTP 404 до того, як навіть спробувати, дуже зручно.

Крім того, для сценарію читання звичайно, що вам просто потрібно, щоб організація спроектувала його на DTO / ViewModel. Наявність сервісного шару між ними часто призводить до того, що багато методів проходять через досить некрасиві.


2
Добре сказано! Моя перевага - викликати сховища, і лише у випадках, коли сховище недостатньо (тобто два об'єкти повинні бути змінені за допомогою різних сховищ), я створюю службу, яка відповідає за цю операцію, і викликаю її з контролера.
Зігімантас

Я помітив досить складний код лише для того, щоб виправдати використання послуги. Абсурд, найменше ...
Gi1ber7

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

Прямий доступ до DAO є небезпечним для контролерів, він може зробити вас сприйнятливим до ін'єкцій SQL і надає доступ до небезпечних дій, таких як ,, deleteAll '', я б точно цього уникав.
Аніруд

4

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

НиколайДанте прокоментував:

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

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

Для того, щоб сприяти гарному розділенню проблем та перевіряемості, сховище повинно бути залежністю, яку ви вводите в службу через конструктор:

IFooRepository repository = new FooRepository();
FooService service = new FooService(repository);

service.DoSomething(...);

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

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

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

Я виявив, що контролер знаходиться в кращому положенні для обробки транзакції або об'єкта Unit Of Work . Потім контролер або об'єкт Unit Of Work делегуватиметься об'єктам служби для складних операцій, або перейде безпосередньо до сховища для простих операцій (наприклад, пошуку запису за Id).

public class ShoppingCartsController : Controller
{
    [HttpPost]
    public ActionResult Edit(int id, ShoppingCartForm model)
    {
        // Controller initiates a database session and transaction
        using (IStoreContext store = new StoreContext())
        {
            // Controller goes directly to a repository to find a record by Id
            ShoppingCart cart = store.ShoppingCarts.Find(id);

            // Controller creates the service, and passes the repository and/or
            // the current transaction
            ShoppingCartService service = new ShoppingCartService(store.ShoppingCarts);

            if (cart == null)
                return HttpNotFound();

            if (ModelState.IsValid)
            {
                // Controller delegates to a service object to manipulate the
                // Domain Model (ShoppingCart)
                service.UpdateShoppingCart(model, cart);

                // Controller decides to commit changes
                store.SaveChanges();

                return RedirectToAction("Index", "Home");
            }
            else
            {
                return View(model);
            }
        }
    }
}

Я вважаю, що поєднання служб та робота із сховищами безпосередньо є цілком прийнятним. Ви можете додатково інкапсулювати транзакцію в об'єкт "Unit Of Work", якщо відчуєте потребу.

Розбиття обов'язків відбувається так:

  • Контролер контролює потік програми
    • Повертає "404 не знайдено", якщо кошик відсутній у базі даних
    • Повторно надає форму з повідомленнями про перевірку, якщо перевірка не вдалася
    • Зберігає кошик для покупок, якщо все перевіряється
  • Контролер делегує до класу обслуговування для виконання бізнес-логіки у ваших Доменних моделях (або об'єктах). Об'єкти обслуговування не повинні реалізовувати ділову логіку! Вони виконують логіку бізнесу.
  • Контролери можуть делегувати безпосередньо сховища для простих операцій
  • Об'єкти сервісу беруть дані у моделі перегляду та делегують доменним моделям для виконання бізнес-логіки (наприклад, сервіс-об’єкт викликає методи доменних моделей перед викликом методів у сховищі)
  • Об'єкти служби делегуються до сховищ для збереження даних
  • Контролери повинні:
    1. Керуйте терміном дії транзакції, або
    2. Створіть об’єкт «Підрозділ роботи», щоб керувати тривалістю транзакції

1
-1 для введення DbContext в контролер, а не в репо. Repo призначений для управління постачальниками даних, тому нікому більше не доведеться, якщо постачальник даних змінює (наприклад, з MySQL на плоскі файли JSON, зміни в одному місці)
Jimmy Hoffa

@JimmyHoffa: Я насправді оглядаю написаний код, і якщо чесно, я створюю "контекстний" об'єкт для моїх сховищ, а не потрібну базу даних. Я думаю DbContext, що це погана назва в даному випадку. Я це зміню. Я використовую NHibernate, а сховищами (або контекстом, якщо це зручно) керують кінцем речей бази даних, тому зміна механізмів збереження не вимагає змін коду поза контекстом.
Грег Бургхардт

ви, мабуть, плутаєте контролер із сховищем за виглядом вашого коду ... Я маю на увазі, у вашому "контексті" все неправильно і абсолютно не повинно існувати в контролері.
Джиммі Хоффа

6
Мені не доведеться відповідати, і я не впевнений, що це хороше питання для початку, але я голосую проти, тому що я вважаю, що ваш підхід - це поганий дизайн. Ніяких важких почуттів, я просто відганяю контексти, які належать контролерам. Контролер IMO не повинен починати та здійснювати такі операції. Це завдання будь-якої кількості інших місць, я вважаю за краще, щоб контролери делегували все, що не просто відповідає HTTP-запиту, десь в іншому місці.
Джиммі Хоффа

1
Сховище, воно, як правило, відповідає за всю інформацію про контекст даних, щоб гарантувати, що більше нічого не потрібно знати про дані, крім того, що повинен знати сам домен
Джиммі Хоффа

1

Це залежить від вашої архітектури. Я використовую Spring, а транзакцій завжди керують сервіси.

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

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

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