Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 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) должен инициировать это действие. У меня, вероятно, была бы информация о конфигурации, которая указывала бы бизнес-объект на соответствующую службу электронной почты для приложения или, в более общем плане, на "уведомляющую" службу, чтобы другие типы уведомлений могли быть добавлены при необходимости.