MVC DDD: Можно ли использовать репозитории вместе со службами в контроллере?
Большую часть времени в служебном коде у меня будет что-то вроде этого:
public SomeService : ISomeService
{
ISomeRepository someRepository;
public Do(int id)
{
someRepository.Do(id);
}
}
так что это вроде излишне
поэтому я начал использовать репозитории прямо в контроллере
это нормально? Есть ли какая-то архитектура, которая делает это?
5 ответов
Вы теряете способность иметь бизнес-логику между ними.
Я не согласен с этим.
Если бизнес-логика там, где она должна быть - в доменной модели, то вызов репо в контроллере (или лучше - использовать связыватель модели для этого), чтобы получить агрегированный корень и вызов метода, кажется мне вполне подходящим.
Прикладные службы должны использоваться, когда слишком много технических деталей связано с тем, что может испортить контроллеры.
В последнее время я видел, как несколько человек упоминали об использовании связующих моделей, чтобы зайти в репо. Откуда эта безумная идея?
Я считаю, что мы говорим здесь о двух разных вещах. Я подозреваю, что "связыватель модели" означает одновременное использование модели в качестве модели представления и привязку измененных значений непосредственно из интерфейса непосредственно к ней (что само по себе неплохо, и в некоторых случаях я бы пошел по этому пути).
Мой "связыватель модели" - это класс, который реализует IModelBinder, который принимает репозиторий в конструкторе (который внедряется и, следовательно, может быть расширен, если нам нужно кэширование с некоторой базовой композицией) и использует его до вызова действия для извлечения совокупного корня и замещать int id
или же Guid id
или же string slug
или же whatever
Аргумент действия с реальным доменным объектом. Объединение этого с аргументом модели представления ввода позволяет нам писать меньше кода. Что-то вроде этого:
public ActionResult ChangeCustomerAddress
(Customer c, ChangeCustomerAddressInput inp){
c.ChangeCustomerAddress(inp.NewAddress);
return RedirectToAction("Details", new{inp.Id});
}
В моем реальном коде это немного сложнее, потому что он включает проверку ModelState и некоторую обработку исключений, которые могут быть выброшены изнутри модели домена (извлечены в метод расширения Controller для повторного использования). Но не намного. Пока что самое длинное действие контроллера составляет ~10 строк.
Вы можете увидеть рабочую реализацию (довольно сложную и (для меня) ненужную сложность) здесь.
Вы просто делаете CRUD-приложения с Linq To Sql или пробуете что-то с реальной доменной логикой?
Как вы можете (надеюсь) увидеть, такой подход фактически заставляет нас перейти к приложениям на основе задач, а не к CRUD.
Делая весь доступ к данным на вашем сервисном уровне и используя IOC, вы можете получить множество преимуществ AOP, таких как невидимое кэширование, управление транзакциями и простое составление компонентов, которые, я не могу себе представить, можно получить с помощью связывателей моделей.
... и имея новый уровень абстракции, который предлагает нам смешивать инфраструктуру с доменной логикой и терять изоляцию доменной модели.
Пожалуйста, просветите меня.
Я не уверен, что сделал. Я не думаю, что я сам просветленный.:)
Вот моя текущая модель связующего базового класса. Вот одно из действий контроллера из моего текущего проекта. А вот и "недостаток" бизнес-логики.
Если вы используете репозитории в своих контроллерах, вы идете прямо с уровня данных на уровень представления. Вы теряете способность иметь бизнес-логику между ними.
Теперь, если вы скажете, что будете использовать Службы только тогда, когда вам нужна бизнес-логика, и будете использовать хранилища повсюду, ваш код станет кошмаром. Уровень представления теперь вызывает как уровень бизнеса, так и уровень данных, и у вас нет хорошего разделения проблем.
Я бы всегда шел по этому пути: Repositories -> Services -> UI
, Как только вы не думаете, что вам нужен бизнес-уровень, требования изменятся, и вам придется переписывать ВСЕ.
Вот эта вещь.
"Бизнес-логика" должна находиться в ваших объектах и объектах стоимости.
Репозитории работают только с AggregateRoots. Таким образом, использование ваших репозиториев непосредственно в ваших контроллерах похоже на то, что вы воспринимаете это действие как "службу". Кроме того, поскольку ваш AggregateRoot может ссылаться на другие AR только по своему идентификатору, вам, возможно, придется позвонить более чем одному репо. Это действительно становится противным очень быстро.
Если вы собираетесь получать услуги, убедитесь, что вы выставляете POCO, а не фактического AggregateRoot и его членов.
Ваш репо не должен выполнять никаких операций, кроме создания, получения, обновления и удаления материала. У вас может быть какое-то настроенное извлечение в зависимости от конкретных условий, но это все. Следовательно, наличие в вашем репо метода, который совпадает с методом в вашем сервисе... код прямо пахнет.
Ваш сервис ориентирован на API. Подумайте об этом... если бы вы упаковали этот сервис в.dll для меня, чтобы вы использовали его, как бы вы создали свои методы таким образом, чтобы мне было легко узнать, что может сделать ваш сервис? Service.Update(объект) не имеет особого смысла.
И я даже не говорил о CQRS... где все становится еще интереснее.
Ваш Web Api - просто КЛИЕНТ вашего Сервиса. Ваш сервис может быть использован другим сервисом, верно? Итак, подумай об этом. Скорее всего, вам понадобится Сервис для инкапсуляции операций в AggregateRoots, обычно путем их создания или извлечения из репозитория, что-то с этим делать, а затем возвращать результат. Обычно.
Имеет смысл?
Мои собственные грубые практики для DDD/MVC:
- контроллеры являются специфичными для приложения, следовательно, они должны содержать только специфичные для приложения методы и вызывать методы Services.
- все общедоступные методы службы обычно являются атомарными транзакциями или запросами
- только службы инстанцируют и вызывают репозитории
- мой домен определяет IContextFactory и IContext (большая утечка абстракции, поскольку члены IContext являются IDBSet)
- у каждого приложения есть своего рода Composition Root, который в основном создает экземпляр фабрики контекста для передачи в службы (вы можете использовать DI-контейнер, но это не так уж важно)
Это заставляет меня держать свой бизнес-код и доступ к данным вне своих контроллеров. Я считаю, что это хорошая дисциплина, учитывая, насколько я свободен, когда не следую вышесказанному!
Даже при "богатой доменной модели" вам все равно понадобится доменная служба для обработки бизнес-логики, которая включает в себя несколько объектов. Я также никогда не видел CRUD без некоторой бизнес-логики, но в простом примере кода. Я всегда хотел бы пойти по пути Мартина, чтобы сохранить мой код простым.