Должны ли контроллеры в веб-приложении ASP.NET MVC вызывать репозитории, службы или оба? - PullRequest
19 голосов
/ 04 декабря 2008

Контроллеры в моем веб-приложении ASP.NET MVC начинают немного раздуты бизнес-логикой. Все примеры в Интернете показывают простые действия контроллера, которые просто извлекают данные из репозитория и передают их в представление. Но что, если вам также необходимо поддерживать бизнес-логику?

Скажем, например, что действие, которое выполняет заказ, также должно отправить электронное письмо. Должен ли я вставить это в контроллер и скопировать / вставить эту логику в любые другие действия, которые также выполняют заказы? Моей первой интуицией было бы создать сервис, подобный OrderFulfillerService, который бы позаботился обо всей этой логике и вызвал бы действие контроллера. Однако для простых операций, таких как получение списка пользователей или заказов из базы данных, я хотел бы напрямую взаимодействовать с хранилищем, а не оборачивать этот вызов службой.

Это приемлемый шаблон дизайна? Действия контроллера вызывают сервисы, когда им нужна бизнес-логика, и репозитории, когда им просто нужен доступ к данным?

Ответы [ 7 ]

26 голосов
/ 04 декабря 2008

Ваши контроллеры (в проекте MVC) должны вызывать ваши объекты в проекте Service. В сервисном проекте обрабатывается вся бизнес-логика.

Хороший пример:

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();
}  

или с инъекцией зависимостей (моя любимая)

// 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();
        }
    }
}

Итак, поехали. Вы можете видеть, что вся логика находится в проектах Service, что означает, что вы можете использовать этот код в других местах.

Где я это узнал?

Из Роб Конери MVC StoreFront носители и учебные пособия . Лучшая вещь, так как нарезанный хлеб. Его уроки объясняют (что я сделал) в деталях с примерами кода полного решения. Он использует Dependency Injection, что теперь SOO kewl, когда я видел, как он использует его в MVC.

НТН.

5 голосов
/ 23 января 2009

Я не уверен в использовании услуг для этого.

Насколько я понимаю, один из принципов DDD (который я сейчас читаю) состоит в том, что доменные объекты организованы в агрегаты и что при создании экземпляра агрегатного корня он может только непосредственно иметь дело с объектами в Агрегате (чтобы поддерживать четкое чувство ответственности).

Создание агрегата должно приводить в исполнение любые инварианты и т. Д.

Если взять пример класса Customer, то Customer может быть корневым элементом Aggregate, а другим классом в Aggregate может быть Address.

Теперь, если вы хотите создать нового клиента, вы можете сделать это, используя конструктор клиента или фабрику. Выполнение этого должно вернуть объект, который полностью функционирует в пределах Агрегатной границы (поэтому он не может работать с Продуктами, поскольку они не являются частью Агрегата, но могут обрабатывать Адреса).

База данных является второстепенной задачей и вступает в действие только для сохранения Агрегата в базе данных или извлечения его из базы данных.

Чтобы избежать непосредственного взаимодействия с базой данных, вы можете создать интерфейс репозитория (как обсуждалось), который с учетом экземпляра Customer (который включает ссылку на Address) должен иметь возможность сохранять Aggregate в базе данных.

Дело в том, что интерфейс репозитория является частью вашей доменной модели / слоя (реализация репозитория - нет). Другой фактор заключается в том, что хранилище, вероятно, должно в итоге вызывать тот же метод «создания», как если бы вы создавали новый объект (для поддержки инвариантов и т. Д.). Если вы используете конструктор, это достаточно просто, так как вы в конечном итоге вызовете конструктор, когда хранилище все равно «создаст» объект из данных.

Прикладной уровень может напрямую связываться с доменом (включая интерфейс репозитория).

Итак, если вы хотите создать новый экземпляр объекта, вы можете, например,

Customer customer = new Customer();

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

Customer customer = _custRepository.GetById(1)

или ...

Customer customer = _custRepository.GetByKey("AlanSmith1")

В конечном итоге он получит экземпляр объекта Customer, который функционирует в пределах своих собственных ограничений и правил, как если бы он создал новый объект Customer напрямую.

Я думаю, что службы должны быть зарезервированы для случая, когда «вещь», с которой вы пытаетесь работать, просто не является объектом. Большинство правил (ограничений и т. Д.) Могут быть написаны как часть самого предметного объекта.

Хороший пример - в DDD Quickly, который я читаю в данный момент. Там у них есть ограничение на объект Книжная полка, в результате чего вы можете добавить только столько книг, сколько может содержать полка.

Вызов метода AddBook для объекта BookShelf проверяет наличие свободного места перед добавлением книги в коллекцию объектов BookShelf. Простой пример, но бизнес-правило применяется самим объектом домена.

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

1 голос
/ 29 июля 2009

Ну, это действительно ваше дело, я хотел бы сохранить контроллеры настолько простыми, насколько это возможно, и для архивации этого мне нужно инкапсулировать бизнес-логику в отдельный слой, и здесь самое главное, в основном у вас есть 2 варианта при условии, что вы используете Linq2SQL или Entity Framework:

  • Вы можете использовать методы расширения и частичный класс для проверки ваших моделей перед сохранением изменений (хуки метод, вы можете увидеть пример это в образце Nerdinner Скотта Gu).

  • Другой путь (и мой любимый, потому что я чувствую больше контроля через поток приложения), это использовать совершенно отдельный слой для бизнеса логика, как сервисный уровень (вы можете увидеть этот прорыв в серии учебники Стивена Вальтера в зона asp.net/mvc).

Этими двумя способами вы получаете СУХОЙ и очищаете свои грязные контроллеры.

1 голос
/ 04 декабря 2008

Может показаться раздражающим, что многое из этого можно найти в бизнес-сервисах:

public Customer GetCustomer(int id)
{
     return customerRepository.Get(id);
}

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

Теперь для очень простого приложения типа CRUD ваши контроллеры могут использовать репозитории напрямую, а не через бизнес-сервисы. У вас все еще может быть что-то вроде EmailerService, но IMO, когда дело доходит до извлечения и работы с сущностями, лучше не смешивать и сопоставлять вызовы бизнес-служб и репозитория в ваших контроллерах.

Что касается того, чтобы объекты (бизнес-объекты) вызывали службы или какие-либо компоненты инфраструктуры, я бы этого не делал. Я предпочитаю хранить объекты POCO и не иметь зависимостей.

1 голос
/ 04 декабря 2008

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

Например, что, если контроллер A вызывает метод на бизнес-уровне для извлечения списка объекта A (и этот метод применяет бизнес-правила - возможно, некоторую фильтрацию или сортировку), но затем появляется контроллер B, которому нужно то же самое часть данных, но забывает о бизнес-уровне и напрямую вызывает уровень данных?

0 голосов
/ 04 декабря 2008

Было бы полезно, если бы мы перестали видеть этот пример снова и снова ...

public ActionResult Index()
{
  var widgetContext = new WidgetDataContext();
  var widgets = from w in widgetContext.Widget
                select w;
  return View(widgets);
}

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

0 голосов
/ 04 декабря 2008

Ваша бизнес-логика должна быть инкапсулирована в бизнес-объекты - если у вас есть объект Order (а у вас есть, не так ли?), А бизнес-правило гласит, что электронное письмо должно отправляться после выполнения заказа, тогда ваш Метод Fulfill (или, если более уместно, установщик для IsFulfilled) должен инициировать это действие. Возможно, у меня была бы информация о конфигурации, которая указывала бы бизнес-объекту на соответствующую службу электронной почты для приложения или, в более общем случае, на службу «уведомителя», чтобы другие типы уведомлений могли быть добавлены, когда / при необходимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...