Как стать автором
Обновить

Комментарии 41

Меня наверно закидают тапками, но считаю что тут переизбыток ООП. В частности я бы выкинул все билдеры, кроме CalendarViewModelBuilder.
Причина — я не верю что на практике будет переиспользован DayViewModelBuilder. Если будет какой-то новый CalendarVisitorsViewModel, то вместо DayViewModel будет использоваться новый DayVisitorsViewModel в котором скорее вместо IEnumerable EventViewModel будет IEnumerable VisitorsDayStats А разбираться что делает билдер внутри билдера, который внутри третьего билдера ради гипотетической возможности переиспользования в будущем как-то не хочется.
Я бы и билдеры выбросил, да и это не билдеры, а фабрики по сути.

Несколько лет назад, когда изучал WPF, мой мозг был затуманен подобными статьями, где обязательно былa ViewModel, даже там, там где она не нужна (например свойства модели тупо копировались во вью-модель). Боюсь на новичков эта статья произведет такой же эффект, как и на меня когда-то, а именно, слепая вера, что вью модель — наше все, а по сути лишняя прокладка.
Был бы пример нагляднее, где без ViewModel ну никак.

Да, наименование так себе. Вот тут подробнее ответил.

Я согласен, что местами модели видов могут выглядеть избыточными, но какое есть решение? Использовать в некоторых местах модели видов (не помещать же все необходимые данные во ViewBag), а в других — «сырые» объекты предметной области? Это очень усложнит чтение кода, он перестанет быть однородным и это бОльшее зло, чем несколько доп. классов, на мой взгляд.
Согласен, что следует подходить к этому вопросу внимательно и не добавлять билдеры там, где они не нужны. Но не могу согласиться с тем, что следует удалить все билдеры, кроме календаря. Повторное использование это постоянное явление. Если снова посмотреть на магазин: товар отображается в списке товаров, в списке рекомендуемых и просмотренных товаров, в корзине, в предыдущих заказах и еще в куче мест. Можно использовать различные представления, но одну модель вида для них всех. Можно управлять глубиной построения модели вида, когда это необходимо. Но это в целом решает 2 важные задачи: избавление от дублирования кода и четкие и понятные рамки ответственности различных классов. В статье я привел очень упрощенный пример, возможно из-за этого сложилось такое мнение.
НЛО прилетело и опубликовало эту надпись здесь
хоть посмотрел на русскоязычную VS :)

тут важно договориться о том, насколько ViewModel'и знают о, собственно, самой модели. Я предпочитаю оставлять ViewModel-классы легковесными DTO, а все знание о моделях концентрируется в контроллерах и AutoMapper конфиге. Вроде связность меньше.
А как в таком случае избегать дублирования кода, когда во многих местах необходимо строить одни и те же модели видов? Все-равно ведь необходимо использовать какой-то класс, который будет содержать необходимый код? Расскажите, пожалуйста, подробнее.
Вообще использование паттерна Builder в данном контексте не правильное, лучше подходит паттерн Factory.

Пример использования Builder`a:

var emailBuilder = new EmailBuilder();
emailBuilder.From(«from@from.from»);
emailBuilder.To(«to@to.to»);

emailBuilder.Build();

причем метод Build() всегда без параметров.
Согласен. Но тут дело не в паттерне, а в названии класса. Билдер в данном случае не означает, что используется шаблон проектирования Строитель. Почему-то так повелось, использовать для построения модели вида билдер, а для обратного процесса — маппер. Я иногда задумывался об этом. Возможно, действительно стоит пересмотреть подход к наименованию, чтобы не было путаницы.
НЛО прилетело и опубликовало эту надпись здесь
Т. к. модели вида не маппятся, а конструируется из разных кусков, лучше все-таки наверное Builder переименовать в Factory, и метод Build переименовать тоже. Тогда это будет вполне отражать суть. А мапперы применяются для обратного процесса — превращения модели вида (например, формы) в модель предметной области.
НЛО прилетело и опубликовало эту надпись здесь
А не проще для этого использовать компоненты (Components)? К примеру я через компонент вывожу дерево комментариев на сайте.
Компоненты как раз и созданы для вывода комментариев или чего-то подобного (т. е. самодостаточных блоков), вы правы. Например, я вывожу блоки меню и форм в своем проекте с использованием компонент. Но модели вида это немного из другой оперы. Например, вы можете в компоненте вывести календарь, и опять упретесь в необходимость передачи данных в его вьюху. Не будете же вы вставлять в компонент еще 42 других компонента? Т. е. это дополняющие друг друга вещи, а не взаимоисключающие. Кстати, есть мнение, что компоненты (а в предыдущих версиях ASP.NET — конструкции вроде Html.Action(), т. е. рендеринг результата некого запроса внутри представления) как бы нарушают концепцию MVC. Нарушают даже не в плохом смысле, а просто как факт.
В общих чертах использую подобный подход. В частности, для каждой View создаю свою ViewModel, при необходимости ViewModel может содержать другие ViewModel.
А вот билдеры, хоть и кажутся на первый взгляд хорошим решением, содержат, на мой взгляд, недостатки. Например, DayViewModelBuilder содержит такую строку:

Events = this.Storage.EventRepository.FilteredByDate(date).Select(
e => new EventViewModelBuilder(this.Storage).Build(e) )
Т.е. для каждого дня месяца будет обращение к Storage для получения списка событий на этот день. Гораздо эффективнее получить события за все необходимые нам дни сразу.
Да, согласен. Здесь следует быть внимательным. Хотя если используется нечто вроде EF с Include то все вложенные сущности могут быть загружены одним запросом.
Тут речь не о вложенных сущностях, а о том, что DayViewModelBuilder знает только об одном дне, для которого и загружает Event.
В этом примере — да. Но чаще бывает вывод чего-то вроде списка товаров, для которых все вложенные вещи обычно можно загрузить при помощи JOIN. Но. Даже в таком случае можно что-то придумать, чтобы выгрузить все мероприятия на месяц за 1 раз. Это уже скорее вопрос оптимизации, чем подхода в целом.
Я в своем первом комментарии как раз сказал, что в целом с вашим подходом согласен. Но вот текущая реализация билдеров, я считаю, создает больше проблем, чем решает.
Добавлю свои пять копеек:
В проектах просто сделали SomeViewModel(SomeModel) конструкторы и все. Достали объект, скормили конструктору и забыли. Сам ViewModel разберется что и куда смапить. Это очень упростило код и при изменении всегда есть только одно место где надо менять. Ну и реюзабельность опять же — скармливаете root объект и все строится по иерархии. Комбинируйте как хотите. Если вдруг надо дополнительный данные — еще параметр в конструкторе. Все зависимости сразу ясны и понятны.
Плюс иногда делается отдельный InputModel, что бы автоматом экранировать входные параметры.
НЛО прилетело и опубликовало эту надпись здесь
Примерно такой же путь. Разве что контекст никогда не давали.
У нас просто ViewModel сейчас нет практически — REST API + SPA. Идет конвертация из Model->Resource и там уже всякие финтифлюшки типа ссылок и другие hypermedia прелести и хитрые json конвертеры.

Я не люблю выставлять модель напрямую так как ViewModel может отличаться от доменной модели. Как раз вы вспомнили про дропдауны и тд.
ViewModel так же может ограничивать данные, доступные во view. Ну и обратный баиндинг туда же.
НЛО прилетело и опубликовало эту надпись здесь
Ну по сути ViewModel в MVC — это специализированный DTO.
Для дропдаунов через всякие вьюбэги данные таскать очень некомильфо имхо :)
Тут есть еще нюанс — как вы данные запроса собираете? Прямо в модель?
НЛО прилетело и опубликовало эту надпись здесь
Ну у нас то же MVC контроллеров штуки 3 осталось ;)
Все данные достаются из разных источников — фс, бд, сервисы и тд. Иногда совпадают, а иногда нет. Бывает необходимость изменения терминологии относительно источника и тд. Ну и hypermedia в полный рост — вариация на тему Json API.
Плюс используется Swagger и генерация клиентов через Autorest. Поэтому только аналог ViewModel И никак иначе.
Пару лет использую anemic model и жизнь стала проще :)
Энтерпрайз решениям важно движение данных и их история, которое лучше всего делается на анемичном домене. ООП в чистом виде — для работы с состояниям объектов, для игр это лучший вариант. Каждой задаче свой инструмент.
НЛО прилетело и опубликовало эту надпись здесь
Унификация уменьшает порог вхождения нового сотрудника, в этом нет ничего плохого.
То, разделение что вы привели, я тоже не увидел предметной области. Видимо она спрятана в «т.д.» У меня в проекте вся логика предметной области находится в отдельном слое. Бизнес-сущности-модели отделены от «технических» моделей. Про «одни и теже вложенные каталоги» с таким не сталкивался, во всём солюшене у меня один класс User, а никак не несколько в разных подпроектах.
Подпроекты между собой должны быть связаны, эта связь и определяет конечный продукт. Но связь должна быть слабой, через DI, чтобы изменения одного подпроекта не были фатальными для других частей.
Если проект становится сложно сопровождать, то видимо вылезают ошибки архитектуры.
НЛО прилетело и опубликовало эту надпись здесь
Мы сейчас активно перенимаем структуру проектов отсюда: Orchard 2. Сборка это не слой приложения, а бизнес фича. Тоесть получаем кучку мини-приложений со стандартной структурой папок и нэйминга :)
НЛО прилетело и опубликовало эту надпись здесь
Вообще говорит.
1. Web — способ хостинга. В первой версии еще и Cli был.
2. Validation — стандартная вариация на Check/Guard и тд.
3. Abstractions — суффикс обозначающий, что сборка содержит только абстракции определенной фичи. Если фичи нет — то общие по проекту.
4. Events- событийная модель.
Это CMS. И для нее это Business Domain. Готовые модули можно поглядеть в первой версии.

ИМХО называть проекты BLL, DAL И тд считаю дурным тоном.
НЛО прилетело и опубликовало эту надпись здесь
Не согласен по поводу дефектов.
Validation — это механизмы валидации — это cross-cutting функциональность, так же как и события.

Мы используем нэйминг вида CompanyName.ProductName.Feature.SubFeature.
Пример:

SomeCompany.SuperProduct.Identity — это основная сборка с реализацией фичи.
SomeCompany.SuperProduct.Identity.Abstractions — контракты и абстракции, которые используются данной фичей и которые можно шарить между сборками.
SomeCompany.SuperProduct.Identity.SqlServer — сабфича для поддержки SqlServer.
SomeCompany.SuperProduct.Identity.Ldap — сабфича для поддержки Ldap директорий.

Структура каждого из проектов однородна:
-Services
-Models
-Events
-Helpers
-Extensions
-ComponentModel
-Configuration
-Controllers
и так далее.

При разработке плагинов разработчик имеет доступ к Abstractions и SubFeatures, Но не имеет доступа к основной имплементации. Так же все эти сборки лежат в Nuget.
Каждая фича — мини приложение, которое полностью изолировано от другого кода и опирается только на базовые абстракции.
Core кстати то же пока есть, но в нем реализации некоторых контрактов из базовых абстракций.
Этот дизайн мы начали использовать еще до Asp.Net Core и он показал себя очень хорошо, особенно если проект развивается не один год и над ним работают как внутренние разработчики, так и разработчики плагинов и расширений.
Фичей так же является кеширование, месаджинг, шина событий и так далее.
Если например надо какой-то внутренний стор реализовать над бд, то это фича приложения — SomeCompany.SuperProduct.Storage.SqlServer, SomeCompany.SuperProduct.Storage.Mongo и так далее.Но фича может сделать opt-out и использовать интеграцию с Oracle для предоставления каких то данных и не использовать внутренний сторадж приложения.

Еще важным плюсом тут является то, что предоставляя контракты для интеграции вы контролируете к чему есть доступ у разработчика и очень легко понять какая часть кода будет затронута тем или иным изменением.
Ну и это оказалось мега удобно, когда решили выделить несколько компонентов в отдельные продукты. Просто нужные кусочки из нагета подтянули и все.
В таком случае, если для построения модели вида используется конструктор, сама модель вида должна обращаться к базе данных для инициализации вложенных моделей видов. Меня это очень смущает. Задача модели вида, на мой взгляд, лишь представлять данные, необходимые для представления, и не больше. А за создание и построение моделей видов должно отвечать нечто внешнее, типа билдера-фабрики. Да, это увеличивает количество классов, но это делает код абсолютно читаемым и в нем очень легко разобраться.
ViewModel в принципе никуда не обращается.
Все данные должны быть переданы в конструкторе. Его задача представить данные в нужном виде для View и не более.
Билдеры размывают ответственность и вносят неопределенность в части того, что вы внезапно можете получить пачку запросов в БД.
В принципе билдер не должен ходить в бд никогда, а все данные для построения принимать из вне ;)
Хотелось бы лучше понять ваш подход, т. к. меня действительно беспокоит возможное количество обращений к базе в предложенном мной решении. Если взять для примера страницу, на которой отображается список объектов А, каждый из которых еще отображает объект Б. И если представить, что объекты А отображаются повсеместно на других страницах, т. е. не зависит именно от страницы списка. И объекты Б также используются отдельно. Каким образом строить иерархические структуры и избегать дублирования кода И множества запросов к БД, если все данные должны передаваться в конструктор? А если вложенность моделей видов еще глубже?
Все просто:
public class VideModel1
{
    public string Prop1 {get;}

    public ViewModel(Model1 model1)
    {
        Prop1 = model1.Prop1;
    }
}

public class VideModel2
{
    public ViewModel1 Model1{get;}

    public string Prop2 {get;}

    public ViewModel2(Model2 model2, Model1 model1)
    {
        Prop2 = model2.Prop2;
        Model1 = new ViewModel1(model1);
    }
}

public class VideModel3
{
    public List<ViewModel2> Models2{get;}

    public string Prop3 {get;}

    public ViewModel3(Model3 model3, params KeyValuePair<Model2, Model1>[] pairs)
    {
        Prop3 = model3.Prop3;
        Models2 = pairs.Select(pair=> new ViewModel2(pair.Key, pair.Value)).ToList();
    }
}


Зачем усложнять? В каждом конкретном методе вы знаете что вам надо достать — пусть метод этим и занимается(ну или сервис какой).
Комбинировать можно как угодно.
Ясно. При таком подходе вы будете снова и снова дублировать логику извлечения объектов откуда-то (например, из базы). Не считая уже того, что для сложных представлений эти конструкторы будут нечитабельными. И для генерации модели вида сложной страницы мы получим здоровенный кусок кода, где все будет в одной куче. Лучше на мой взгляд использовать возможности, вроде Include, для загрузки всего графа объектов доменной модели одним запросом, чтобы не обращаться к БД более 1го раза. Или же делать некий специальный объект-источник всех необходимых доменных объектов, откуда их могут брать все модели вида, подлежащие рендерингу при текущем запросе.
1. Что значит дублировать? Никогда не сталкивался с такими проблемами. Это вопрос к организации получения данных(контроллер/сервисный слой).
2. С чего вы взяли? Если у вас есть Aggregate Root — достаньте его в контроллере и скормите в конструктор с одним параметром.
Если вам надо по каким то условиям еще что-то доставать во время создания ViewModel — то это ошибка дизайна.
То, какие данные отдать клиенту — это знание контроллера/сервиса. И это знание не должно утекать в билдеры или ViewModel.

Про инклюд вы правильно сказали. Если у вас EF — собирайте все в одном объекте и отдайте ее в конструктор. А он пусть просто рассует то, что ему дали.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации