2012-02-06 1 views
1

Это вопрос архитектуры.в хорошо построенной системе cms-типа, где в архитектуре должен быть создан бизнес-объект?

Предположим, что я создал многоуровневую систему в ASP.NET MVC с хорошим уровнем домена, который использует шаблон репозитория для доступа к данным. Одним из этих объектов домена является Product. На стороне CMS у меня есть представление о создании и редактировании продуктов. И у меня есть интерфейс, на котором должен отображаться этот продукт. И эти взгляды значительно отличаются друг от друга, поэтому для них подходит другая модель.

1) Где должен быть создан новый объект продукта, когда пользователь вводит данные для нового продукта в виде ввода данных? В контроллере? Но заставить диспетчера отвечать за создание объекта может повредить принцип единой ответственности. Или следует использовать заводскую модель? Это означало бы, что завод будет очень специфичным, поскольку введенные данные будут переданы «как есть» на заводе. Поэтому кодирование с помощью IProductFactory было бы невозможным, поскольку входные данные специфичны для этого представления ввода данных. Так ли верно, что у нас есть плотная связь между этим контроллером и фабрикой?

2) Модель просмотра продукта должна отображаться на интерфейсе, откуда это должно произойти? Ответ мне кажется ViewModelFactory, который принимает объект домена Product и создает из него представление. Но опять же, этот viewmodelfactory был бы конкретным для этой точки зрения, потому что рассматриваемая модель зрения является конкретной. Так правильно, что контроллер и viewmodelfactory будут тесно связаны?

3) Проверка: входные данные должны быть проверены во внешнем интерфейсе, но уровень домена должен также проверять продукт (поскольку уровень домена ничего не знает об пользовательском интерфейсе и даже не знает, что пользовательский интерфейс выполняет валидацию и, следовательно, должен не зависит от проверки там). но где должна проходить проверка? ProductFactory кажется хорошим выбором; мне кажется, что это относится к SRP, если задача фабрики продуктов описана как «создание допустимых объектов продукта». Но, возможно, бизнес-объект Product должен содержать проверку. Это кажется более уместным, поскольку валидация продукта будет не только необходима во время создания, но и в других местах. Но как мы можем проверить продукт, который еще не создан? Должен ли бизнес-объект Product иметь такие методы, как IsNameValid, IsPriceValid и т. Д.?

ответ

3

Сначала я отвечу на ваш второй вопрос.

Да, режимы просмотра должны быть плотно связаны с контроллером. Однако вам не нужно ViewModelFactory. Что-то вроде AutoMapper или ValueInjecter должно быть достаточно хорошим для преобразования продукта Domain в ProductViewModel.

Что касается вашего первого вопроса, вы должны сохранить свой продукт Product Factory отдельно от своего контроллера. Вы можете использовать несколько разных подходов. Можно было бы создать фабричный метод, который принимает только скалярные значения как аргументы метода - например, string, bool и т. Д., Другие примитивы и чистые типы .NET.

Затем вы можете передать ваш контроллер скалярным методам из режима просмотра. Это слабо взаимосвязано и очень сплоченно.

Например:

[HttpPost] 
public ActionResult CreateProduct(ProductViewModel model) 
{ 
    if (ModelState.IsValid) 
    { 
     // assuming product factory is constructor-injected 
     var domainProduct = _productFactory.BuildProduct(
      model.Name, model.Price, model.Description); 
     // ... eventually return a result 
    } 
    return View(model); 
} 

Другой подход положить методы для передачи свойств ViewModel непосредственно на объекте домена, но для этого подхода, то лучше всего, чтобы сделать ваши сеттеры собственности непубличный:

[HttpPost] 
public ActionResult CreateProduct(ProductViewModel model) 
{ 
    if (ModelState.IsValid) 
    { 
     // assuming no product factory 
     var domainProduct = new Domain.Product(); 
     domainProduct.SetName(model.Name); 
     domainProduct.SetPrice(model.Price); 
     domainProduct.SetDescription(model.Description); 
     // ... eventually return a result 
    } 
    return View(model); 
} 

Я предпочитаю первый вариант, потому что он менее подробный и сохраняет создание объекта в вашем доменном слое. Однако оба метода слабо связаны, потому что вы не используете типы viewmodel между вашим уровнем MVC и вашим доменным уровнем. Вместо этого ваш более высокий уровень (MVC) принимает зависимость на уровне домена, но ваш уровень домена свободен от всех проблем MVC.

Ответ на первых 2 комментария

Второй комментарий первым, повторно проверка: Это не обязательно должна быть ответственность Продукт завода для обеспечения проверки, но если вы хотите, чтобы обеспечить соблюдение бизнес-правил в области, валидация должна произойти на заводе или ниже. Например, фабрика продуктов может создавать экземпляр продукта, а затем делегировать операции сборки методам объекта - аналогично вышеописанным методам SetXyzProperty (разница между этими методами может быть internal для домена lib вместо public). В этом случае ответственность за соблюдение валидации будет зависеть от субъекта продукта.

Если вы делаете исключения для принудительной проверки, они будут пузыриться через фабрику и в контроллер. Это то, что я обычно пытаюсь сделать. Если бизнес-правило когда-либо заканчивается барботажем контроллеру, то это означает, что MVC не имеет правила проверки, а ModelState.IsValid должен быть ложным. Кроме того, таким образом вам не нужно беспокоиться о передаче сообщений с фабрики - нарушения бизнес-правил будут иметь форму исключения.

Что касается вашего первого комментария, да: MVC принимает зависимость от домена, а не наоборот. Если вы хотите передать модель представления на завод, ваш домен будет зависеть от того, в какой lib находится класс viewmodel (который должен быть MVC). Это правда, что у вас может быть множество аргументов фабричного метода или взрыв перегрузки фабричных методов. Если вы обнаружите, что это происходит, было бы лучше разоблачить более детализированные методы для самой сущности, чем полагаться на фабрику.

Например, у вас может быть форма, в которой пользователь может быстро нажать, чтобы изменить только имя или цену Продукта, не пройдя всю форму. Это действие может произойти даже через ajax, используя JSON вместо полного POST-браузера. Когда контроллер обрабатывает его, может быть проще просто вызвать myProduct.SetPriceOrName(object priceOrName) вместо productFactory.RePriceOrRename(int productId, object priceOrName).

Ответ на вопрос обновления

Другие могут иметь разные мнения, но в шахте, бизнес-домен не должен подвергать проверки API. Это не значит, что у вас не может быть метода IsValidPrice для объекта. Тем не менее, я не думаю, что это должно быть раскрыто как часть публичного API. Рассмотрим следующий пример:

namespace NinetyNineCentStore.Domain 
{ 
    public class Product 
    { 
     public decimal Price { get; protected set; } 
     public void SetPrice(decimal price) 
     { 
      ValidatePrice(price); 
      Price = price; 
     } 
     internal static bool IsPriceValid(decimal price) 
     { 
      return IsPriceAtLeast99Cents(price) 
       && IsPriceAtMostNineteen99(price) 
       && DoesPriceEndIn99Cents(price); 
     } 
     private static bool IsPriceAtLeast99Cents(decimal price) 
     { 
      return (price >= 0.99m); 
     } 
     private static bool IsPriceAtMostNineteen99(decimal price) 
     { 
      return (price <= 19.99m); 
     } 
     private static bool DoesPriceEndIn99Cents(decimal price) 
     { 
      return (price % 1 == 99); 
     } 
     private static void ValidatePrice(decimal price) 
     { 
      if (!IsPriceAtLeast99Cents(price)) 
       throw new InvalidOperationException(
        "Product price must be at least 99 cents."); 
      if (!IsPriceAtMostNineteen99(price)) 
       throw new InvalidOperationException(
        "Product price must be no greater than 19.99."); 
      if (!DoesPriceEndIn99Cents(price)) 
       throw new InvalidOperationException(
        "Product price must end with 99 cents."); 
     } 
    } 
} 

выше инкапсулирует проверки на объект, не подвергая его в API. Ваша фабрика все еще может ссылаться на internal IsPriceValid, но не нуждается в перестановке каждого небольшого бизнес-правила. Когда любой клиент, внутренний или публичный, пытается нарушить правило, генерируется исключение.

Этот шаблон может показаться излишним, но рассмотрите бизнес-правила, в которых задействовано более одного объекта. Например, скажем, вы можете нарушить правило DoesPriceEndIn99Cents, когда Product.IsOnSale == true. У вас уже есть инкапсуляция ValidatePrice, поэтому вы можете разместить это правило, не открывая новый метод API проверки достоверности.

+0

Спасибо за ясный и полезный ответ. Некоторые следствия, которые я вывел: 1) Productfactory может иметь несколько перегрузок, потому что не все данные могут быть доступны каждый раз 2) Основная перегрузка может иметь довольно некоторые параметры, но это не должно быть проблемой, потому что задача фабрики ясна. Но, конечно, мы не можем передать viewmodel как только один параметр для productfactory, только потому, что это выглядит более удобным. 3) Viewmodelfactories являются частью уровня пользовательского интерфейса. – staccata

+0

. Я удалил свой второй комментарий и поместил его как 3) в исходный вопрос – staccata

Смежные вопросы