Должны ли контроллеры в веб-приложении ASP.NET MVC вызывать репозитории, службы или и то, и другое?
Контроллеры в моем веб-приложении ASP.NET MVC начинают немного раздуваться бизнес-логикой. Примеры в Интернете показывают простые действия контроллера, которые просто вытаскивают данные из репозитория и передают его в представление. Но что, если вам нужно также поддерживать бизнес-логику?
Скажем, например, действие, выполняющее заказ, также необходимо отправить электронное сообщение. Я придерживаюсь этого в контроллере и копирую/вставляю эту логику в любые другие действия, которые также выполняют заказы? Моей первой интуицией было бы создать такую услугу, как OrderFulfillerService, которая позаботится обо всей этой логике и вызовет действие контроллера. Однако для простых операций, таких как получение списка пользователей или заказов из базы данных, я хотел бы напрямую взаимодействовать с репозиторием, вместо того, чтобы этот вызов был обернут службой.
Является ли это приемлемым шаблоном проектирования? Действия контроллера вызывают услуги, когда им нужна бизнес-логика и репозитории, когда им просто нужен доступ к данным?
Ответы
Ответ 1
Ваши контроллеры (в проекте MVC) должны вызывать ваши объекты в проекте службы. В проекте услуг используется вся бизнес-логика.
Хорошим примером является следующее:
public ActionResult Index()
{
ProductServices productServices = new ProductServices();
// top 10 products, for example.
IList<Product> productList productServices.GetProducts(10);
// Set this data into the custom viewdata.
ViewData.Model = new ProductViewData
{
ProductList = productList;
};
return View();
}
или с впрыском зависимостей (my fav)
// Field with the reference to all product services (aka. business logic)
private readonly ProductServices _productServices;
// 'Greedy' constructor, which Dependency Injection auto finds and therefore
// will use.
public ProductController(ProductServices productServices)
{
_productServices = productServices;
}
public ActionResult Index()
{
// top 10 products, for example.
// NOTE: The services instance was automagically created by the DI
// so i din't have to worry about it NOT being instansiated.
IList<Product> productList _productServices.GetProducts(10);
// Set this data into the custom viewdata.
ViewData.Model = new ProductViewData
{
ProductList = productList;
};
return View();
}
Теперь, какой проект службы (или что такое ProductServices)? что библиотека классов с вашей бизнес-логикой. Например.
public class ProductServices : IProductServices
{
private readonly ProductRepository _productRepository;
public ProductServices(ProductRepository productRepository)
{
_productRepository = productRepository;
}
public IList<Product> GetProducts(int numberOfProducts)
{
// GetProducts() and OrderByMostRecent() are custom linq helpers...
return _productRepository.GetProducts()
.OrderByMostRecent()
.Take(numberOfProducts)
.ToList();
}
}
но это может быть так хардкорно и запутанно... поэтому простая версия класса ServiceProduct может быть (но я бы не рекомендовал)...
public class ProductServices
{
public IList<Product> GetProducts(int numberOfProducts)
{
using (DB db = new Linq2SqlDb() )
{
return (from p in db.Products
orderby p.DateCreated ascending
select p).Take(10).ToList();
}
}
}
Итак, вы идете. Вы можете видеть, что вся логика находится в проектах службы, что означает, что вы можете повторно использовать этот код в других местах.
Где я узнал об этом?
Из Роб Коньери MVC StoreFront и tutorials. Лучше всего, так как нарезанный хлеб.
Его учебники объясняют (что я сделал) в полной мере с примерами полного кода решения. Он использует Injection Dependency, который теперь является SOO kewl, когда я видел, как он его использует, в MVC.
НТН.
Ответ 2
Я не уверен в использовании сервисов для этого.
Как я понимаю, один из принципов DDD (который я сейчас читаю) заключается в том, что объекты домена организованы в Aggregates и что, когда вы создаете экземпляр корня Aggregate, он может напрямую обращаться с объектами в Агрегате (чтобы поддерживать четкое чувство ответственности).
Создание агрегата должно обеспечивать соблюдение любых инвариантов и т.д.
Взяв пример класса Customer, Клиент может быть корневым агрегированием, а другой класс в Агрегате может быть адресом.
Теперь, если вы хотите создать нового Клиента, вы должны сделать это, используя либо конструктор Customer, либо factory. Это должно возвращать объект, который полностью функционирует в пределах границы агрегата (поэтому он не может иметь дело с Продуктами, поскольку они не являются частью Агрегата, но могут обрабатывать Адреса).
База данных является второстепенной задачей и только вступает в игру для сохранения Агрегата в базе данных или извлечения из базы данных.
Чтобы избежать взаимодействия с базой данных напрямую, вы можете создать интерфейс репозитория (как обсуждалось), который задал экземпляр Клиента (который включает ссылку на адрес), должен иметь возможность сохранять Агрегат в базе данных.
Дело в том, что интерфейс репозитория является частью вашей модели/уровня домена (реализация Репозитория не является). Другим фактором является то, что репозиторий, вероятно, должен в конечном итоге вызвать тот же метод "create", как если бы вы создавали новый объект (для сохранения инвариантов и т.д.). Если вы используете конструктор, это достаточно просто, так как вы в конечном итоге вызываете конструктор, когда репозиторий "создает" объект из данных в любом случае.
Уровень приложения может напрямую связываться с доменом (включая интерфейс репозитория).
Итак, если вы хотите создать новый экземпляр объекта, вы можете, например,
Customer customer = new Customer();
Если приложение должно получить экземпляр клиента из репозитория, нет никакой особой причины, о которой я могу думать, чтобы он не вызывал...
Customer customer = _custRepository.GetById(1)
или...
Customer customer = _custRepository.GetByKey("AlanSmith1")
В конечном итоге это закончится экземпляром объекта Customer, который будет функционировать в пределах своих собственных ограничений и правил так же, как если бы он создал новый объект Customer напрямую.
Я думаю, что сервисы должны быть зарезервированы, если "вещь", с которой вы пытаетесь работать, просто не является объектом. Большинство правил (ограничений и т.д.) Могут быть записаны как часть самого объекта домена.
Хорошим примером является DDD Quickly pdf, который я читаю на данный момент. Там они имеют ограничение на объект Bookshelf, в котором вы можете добавлять только столько книг, сколько может содержать полка.
Вызов метода AddBook объекта BookShelf проверяет, доступно ли пространство перед добавлением книги в коллекцию BookShelf объектов Book. Простой пример, но бизнес-правило выполняется самим объектом домена.
Я не говорю, что все из вышеперечисленного верно, кстати! Сейчас я пытаюсь разобраться во всем этом!
Ответ 3
Если у вас будет бизнес-уровень, я думаю, что лучше всего, чтобы только уровень бизнес-уровня обменивался данными с уровнем данных. Я могу понять, почему в простом случае вы должны иметь слой презентации (контроллер) напрямую с уровнем данных, но как только вы идентифицируете необходимость изолированного бизнес-уровня, тогда смешивание использования двух уровней на более высоких уровнях быть опасным.
Например, что, если контроллер A вызывает метод на бизнес-уровне для извлечения списка объектов A (и этот метод применяет бизнес-правила - возможно, некоторые фильтрации или сортировки), но затем идет контроллер B, он нуждается в том же кусок данных, но забывает о бизнес-слое и вызывает слой данных напрямую?
Ответ 4
В бизнес-сервисах может показаться неприятным видеть:
public Customer GetCustomer(int id)
{
return customerRepository.Get(id);
}
И естественно, что у вас есть сильное желание обойти службу. Но вам лучше в долгосрочной перспективе, позволяя вашим бизнес-сервисам быть промежуточными между контроллерами и репозиториями.
Теперь, для очень простого приложения типа CRUD, вы могли бы использовать ваши контроллеры для хранения репозиториев напрямую, вместо того чтобы проходить через бизнес-службы. У вас все еще может быть что-то вроде EmailerService, но IMO, когда дело доходит до получения и работы с объектами, лучше всего не смешивать и сопоставлять вызовы бизнес-служб и репозиториев в ваших контроллерах.
Что касается того, что сущности (бизнес-объекты) вызывают службы или какие-либо компоненты инфраструктуры, я бы этого не сделал. Я предпочитаю сохранять сущности POCO и без зависимостей.
Ответ 5
Это помогло бы, если бы мы могли перестать видеть этот пример снова и снова...
public ActionResult Index()
{
var widgetContext = new WidgetDataContext();
var widgets = from w in widgetContext.Widget
select w;
return View(widgets);
}
Я понимаю, что это не полезно для вашего вопроса, но, похоже, это часть многих демо-продуктов, которые, я думаю, могут вводить в заблуждение.
Ответ 6
Ну, это действительно зависит от вас, мне нравится держать контроллеры как можно более простыми, и для архивирования мне нужно инкапсулировать логику бизнес-процессов в отдельный слой, а вот большая вещь, в основном у вас есть 2 варианта, предполагая, что вы используете Linq2SQL или Entity Framework:
-
Вы можете использовать методы расширителя и
частичный класс для проверки ваших моделей
непосредственно перед сохранением изменений (крючки
метода, вы можете увидеть пример
это в примере Nerdinner от Scott
Gu).
-
Другой способ (и мой любимый,
потому что я чувствую больший контроль
над потоком приложения), заключается в использовании
полностью отдельный слой для предприятий
логики, например, "Уровень обслуживания" (вы можете
см. этот aprroach в серии
учебники Стивена Вальтера в
asp.net/mvc).
С помощью этих двух методов вы получили DRY и очистите свои грязные контроллеры.
Ответ 7
Ваша бизнес-логика должна быть инкапсулирована в бизнес-объекты - если у вас есть объект Order (и вы это делаете, не так ли?), а в бизнес-правиле указано, что электронное письмо должно быть отправлено, когда заказ выполнен, тогда ваш Метод Fulfill (или, если это более уместно, установщик для IsFulfilled) должен инициировать это действие. У меня, вероятно, была бы информация о конфигурации, которая указывала бы бизнес-объект на соответствующую службу электронной почты для приложения или, в более общем плане, на "уведомляющую" службу, чтобы другие типы уведомлений могли быть добавлены при необходимости.