Это плохая практика, что хранилище вызовов контроллера вместо службы?

44

Это плохая практика, что хранилище вызовов контроллера вместо службы?

объяснить больше:

Я выяснил, что в хорошем дизайне контроллеры называют сервис, а сервис используют репозиторий.

но иногда в контроллере у меня нет / нужна какая-то логика и мне просто нужно извлечь из базы данных и передать ее для просмотра.

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

mohsenJsh
источник
Как вы звоните в службу? Через интерфейс REST?
Роберт Харви
2
Я использую этот подход к проектированию сам довольно часто. Мой контроллер (или базовый класс композитора) будет запрашивать данные или отправлять данные в репозиторий, а затем передавать их любым классам обслуживания, которые необходимо обработать. Нет смысла объединять классы обработки данных с классами извлечения / управления данными, это разные проблемы, хотя я знаю, что типичный подход - это делать так.
Джимми Хоффа
3
Мех. Если это небольшое приложение, и вы просто пытаетесь получить данные из базы данных, уровень обслуживания является пустой тратой времени, если этот уровень обслуживания не является частью какого-либо общедоступного API, такого как интерфейс REST. "Молоко полезно для вас или плохо для вас?" Зависит от того, не переносите ли вы лактозу.
Роберт Харви
4
Не существует жесткого и быстрого правила, согласно которому вы должны иметь структуру Controller -> Service -> Repository, а не Controller -> Repository. Выберите правильный шаблон для правильного применения. Я бы сказал, что вы должны сделать вашу заявку последовательной.
NikolaiDante
Может быть, вы могли бы придумать универсальный сервис, который только перенаправляет ваш запрос в хранилище и затем возвращает. Это может быть полезно для сохранения единого интерфейса и будет простым, если в будущем вам потребуется добавить реальный сервис, чтобы сделать что-то перед вызовом хранилища.
Энрике Барселуш

Ответы:

32

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

Если сущности, которые вы извлекаете через хранилище, обрабатывают большую часть бизнес-логики, другие службы не нужны. Достаточно просто иметь хранилище.

Даже если у вас есть некоторые услуги, через которые вы должны пройти, чтобы манипулировать вашими сущностями. Сначала извлеките объект из хранилища, а затем передайте его указанному сервису. Возможность бросить HTTP 404 даже прежде, чем пытаться, очень удобна.

Также для сценария чтения это обычное явление, вам просто нужно, чтобы объект проецировал его в DTO / ViewModel. Наличие промежуточного уровня обслуживания часто приводит к многочисленным проходам через методы, что довольно уродливо.

Joppe
источник
2
Хорошо сказано! Я предпочитаю вызывать репозитории, и только в случаях, когда репозитория недостаточно (т. Е. Необходимо изменить две сущности с помощью разных репозиториев), я создаю службу, которая отвечает за эту операцию, и вызываю ее из контроллера.
Зигимантас
Я заметил довольно сложный код, чтобы оправдать использование сервиса. Абсурд, минимум ...
Gi1ber7
Таким образом, мой репозиторий возвращает список «бизнес-объектов», которые мне нужно преобразовать в «объекты XML». Достаточно ли этой причины для создания сервисного уровня? Я вызываю метод для каждого объекта, чтобы преобразовать его в другой тип и добавить в новый список.
bot_bot
Прямой доступ DAO опасен в контроллерах, он может сделать вас уязвимыми для SQL-инъекций и дает доступ к опасным действиям, таким как ,, deleteAll '. Я бы определенно избегал этого.
Анируд
4

Для контроллера не является плохой практикой прямой вызов хранилища. «Сервис» - это просто еще один инструмент, поэтому используйте его там, где это имеет смысл.

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

... Выберите правильный шаблон для правильного применения. Я бы сказал, что вы должны сделать вашу заявку последовательной.

Я не думаю, что последовательность является наиболее важным аспектом. Класс «service» предназначен для инкапсуляции некоторой логики более высокого уровня, поэтому контроллеру не нужно его реализовывать. Если для данной операции не требуется «логика более высокого уровня», просто перейдите непосредственно в хранилище.

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

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

service.DoSomething(...);

Если для поиска записей в базе данных требуется какой-то параметризованный запрос, класс обслуживания мог бы быть хорошим местом для использования в вашей модели представления и построения запроса, который затем выполняется хранилищем.

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

Если вы движетесь в противоположном направлении, если вашему контроллеру необходимо получить запись по его идентификатору, то делегирование объекту службы для этого похоже на попадание в чертёж кувалдой - это намного больше, чем вам нужно.

Я обнаружил, что контроллер находится в лучшем положении для обработки транзакции или объекта 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 Not Found», если корзина покупок отсутствует в базе данных
    • Повторно отображает форму с сообщениями проверки, если проверка не удалась
    • Сохраняет корзину, если все проверено
  • Контроллер делегирует классу обслуживания для выполнения бизнес-логики в ваших моделях домена (или объектах). Сервисные объекты не должны реализовывать бизнес-логику! Они выполняют бизнес-логику.
  • Контроллеры могут делегировать напрямую в репозитории для простых операций
  • Сервисные объекты берут данные в модели представления и делегируют моделям домена для выполнения бизнес-логики (например, сервисный объект вызывает методы в моделях домена перед вызовом методов в хранилище)
  • Сервисные объекты делегируют в репозитории для сохранения данных
  • Контроллеры должны либо:
    1. Управлять временем жизни транзакции или
    2. Создайте объект Unit Of Work для управления временем жизни транзакции.
Грег Бургхардт
источник
1
-1 для помещения DbContext в контроллер, а не в репозиторий. Репо предназначено для управления поставщиками данных, поэтому больше никто не должен в случае смены поставщика данных (например, с MySQL на плоские файлы JSON, смена в одном месте)
Джимми Хоффа,
@JimmyHoffa: На самом деле я оглядываюсь назад на код, который я написал, и, честно говоря, я создаю объект «context» для своих репозиториев, а не базу данных. Я думаю, что DbContextэто плохое имя в этом случае. Я изменю это. Я использую NHibernate, а репозитории (или контекст, если это удобно) управляют конечной целью базы данных, поэтому изменение механизмов сохранения не требует изменений кода вне контекста.
Грег Бургхардт
Вы, кажется, путаете контроллер с репозиторием из-за внешнего вида вашего кода ... Я имею в виду, что ваш "контекст" неверен и абсолютно не должен существовать в контроллере.
Джимми Хоффа
6
Мне не нужно отвечать, и я не уверен, что это хороший вопрос для начала, но я отказываюсь голосовать, потому что я думаю, что ваш подход - плохой дизайн. Никаких обид, я просто не одобряю контексты, которыми владеют контроллеры. IMO контроллер не должен запускать и совершать транзакции, подобные этой. Это работа любого количества других мест, я предпочитаю, чтобы контроллеры делегировали все, что не просто выполняет HTTP-запрос, где-то еще.
Джимми Хоффа
1
Хранилище, как правило, отвечает за всю информацию о контексте данных, чтобы гарантировать, что больше ничего не нужно знать о проблемах данных, кроме того, о чем должен знать сам домен
Джимми Хоффа,
2

Это зависит от вашей архитектуры. Я использую Spring, а транзакционность всегда управляется сервисами.

Если вы вызываете репозитории напрямую для операций записи (или простых сервисов без логики, которые просто делегируют репозиторию), возможно, вы используете несколько транзакций базы данных для операции, которая должна быть выполнена в одной. Это приведет к несогласованности данных в вашей базе данных. Как правило, операции с базой данных должны работать или должны завершаться неудачей, но неполучающие операции являются причиной головной боли.

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

Rober2D2
источник