MVC и NOSQL: сохранение моделей просмотра непосредственно в MongoDB? - PullRequest
7 голосов
/ 27 июня 2011

Я понимаю, что "правильной" структурой для разделения проблем в MVC является наличие моделей представлений для структурирования ваших представлений и отдельных моделей данных для сохранения в выбранном вами хранилище. Я начал экспериментировать с MongoDB и начинаю думать, что это может не применяться при использовании базы данных без схемы в стиле NO-SQL. Я хотел представить этот сценарий сообществу stackoverflow и посмотреть, что думают все. Я новичок в MVC, так что для меня это имело смысл, но, может быть, я что-то упускаю ...

Вот мой пример для этого обсуждения: когда пользователь хочет редактировать свой профиль, он переходит в представление UserEdit, которое использует модель UserEdit ниже.

public class UserEditModel
{
    public string Username
    {
        get { return Info.Username; }
        set { Info.Username = value; }
    }

    [Required]
    [MembershipPassword]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [DataType(DataType.Password)]
    [DisplayName("Confirm Password")]
    [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }

    [Required]
    [Email]
    public string Email { get; set; }

    public UserInfo Info { get; set; }
    public Dictionary<string, bool> Roles { get; set; }
}

public class UserInfo : IRepoData
{
    [ScaffoldColumn(false)]
    public Guid _id { get; set; }

    [ScaffoldColumn(false)]
    public DateTime Timestamp { get; set; }

    [Required]
    [DisplayName("Username")]
    [ScaffoldColumn(false)]
    public string Username { get; set; }

    [Required]
    [DisplayName("First Name")]
    public string FirstName { get; set; }

    [Required]
    [DisplayName("Last Name")]
    public string LastName { get; set; }

    [ScaffoldColumn(false)]
    public string Theme { get; set; }

    [ScaffoldColumn(false)]
    public bool IsADUser { get; set; }
}

Обратите внимание, что класс UserEditModel содержит экземпляр UserInfo, который наследуется от IRepoData? UserInfo - это то, что сохраняется в базе данных. У меня есть общий класс репозитория, который принимает любой объект, который наследует форму IRepoData, и сохраняет его; так что я просто звоню Repository.Save(myUserInfo) и все готово. IRepoData определяет _id (соглашение об именах MongoDB) и метку времени, поэтому репозиторий может выполнять резервирование на основе _id и проверять наличие конфликтов на основе метки времени и любых других свойств, которые объект только что сохранил в MongoDB. Представление, по большей части, просто нужно использовать @Html.EditorFor, и мы готовы к работе! По сути, все, что нужно только представлению, входит в базовую модель, все, что нужно только хранилищу, просто получает аннотацию [ScaffoldColumn(false)], а все остальное является общим для этих двух. (Кстати, имя пользователя, пароль, роли и электронная почта сохраняются для поставщиков .NET, поэтому их нет в объекте UserInfo.)

большие преимущества этого сценария являются двоякими ...

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

  2. Я могу пересмотреть в секундах ... Если мне нужно добавить второй адрес электронной почты, я просто добавляю его в объект UserInfo - он добавляется в представление и сохраняется в хранилище, просто добавив одно свойство к объекту. Поскольку я использую MongoDB, мне не нужно изменять схему БД или связываться с какими-либо существующими данными.

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

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

Ответы [ 2 ]

9 голосов
/ 28 июня 2011

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

Одна из важных вещей - это проблемы бизнес-логики / целостности данных с использованием того же класса для моделирования / персистентности данных, который используется в представлениях. Возьмите ситуацию, когда у вас есть свойство DateTime DateAdded в вашем пользовательском классе, чтобы указать, когда пользователь был добавлен. Если вы предоставляете форму, которая подключается прямо к вашему UserInfo классу, вы получаете обработчик действий, который выглядит следующим образом:

[HttpPost]
public ActionResult Edit(UserInfo model) { }

Скорее всего, вы не хотите, чтобы пользователь мог изменять, когда они были добавлены в систему, поэтому ваша первая мысль - не предоставлять поле в форме.

Однако нельзя полагаться на это по двум причинам. Во-первых, значение для DateAdded будет таким же, как то, что вы получите, если бы вы сделали new DateTime(), или оно будет null (в любом случае для этого пользователя будет неверно).

Вторая проблема заключается в том, что пользователи могут подделать это в запросе формы и добавить &DateAdded=<whatever date> к данным POST, и теперь ваше приложение изменит поле DateAdded в БД на любое введенное пользователем.

Это сделано специально, поскольку механизм привязки модели MVC просматривает данные, отправленные через POST, и пытается автоматически связать их с любыми доступными свойствами в модели. Он не может знать, что свойство, которое было отправлено, не было в исходной форме, и, таким образом, оно все равно будет связывать его с этим свойством.

У ViewModels нет этой проблемы, потому что ваша модель представления должна знать, как преобразовать себя в / из объекта данных, и у нее нет поля DateAdded для подмены, она имеет только минимальные поля, необходимые для отображения (или получить) это данные.

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

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

public DateTime? BannedDate { get; set; }
public DateTime? ActivationDate { get; set; } // Date the account was activated via email link

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

// In status column of the web page's data grid

@if (user.BannedDate != null)
{
    <span class="banned">Banned</span>
}
else if (user.ActivationDate != null)
{
    <span class="Activated">Activated</span>
}

//.... Do some html to finish other columns in the table
// In the Actions column of the web page's data grid
@if (user.BannedDate != null)
{
    // .. Add buttons for banned users
}
else if (user.ActivationDate != null)
{
    // .. Add buttons for activated  users
}

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

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

Тогда приведенный выше код упрощается как:

// In status column of the web page's data grid

@if (user.Status == UserStatuses.Banned)
{
    <span class="banned">Banned</span>
}
else if (user.Status == UserStatuses.Activated)
{
    <span class="Activated">Activated</span>
}

//.... Do some html to finish other columns in the table
// In the Actions column of the web page's data grid
@if (user.Status == UserStatuses.Banned)
{
    // .. Add buttons for banned users
}
else if (user.Status == UserStatuses.Activated)
{
    // .. Add buttons for activated  users
}

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

7 голосов
/ 28 июня 2011

tl; dr

В приложении есть как минимум 3 слоя моделей, иногда их можно безопасно комбинировать, иногда нет.В контексте вопроса можно объединить модели персистентности и предметной области, но не модель представления.

full post

Описанный вами сценарий одинаково хорошо подходит для любой модели сущностей.Это может быть использование модели Linq2Sql в качестве модели ViewModel, модели структуры сущностей, модели спящего режима и т. Д. Главное, что вы хотите использовать постоянную модель непосредственно в качестве модели представления.Разделение интересов, как вы упоминаете, явно не заставляет вас избегать этого.На самом деле разделение интересов не является даже самым важным фактором при построении слоев модели.

В типичном веб-приложении есть как минимум 3 различных уровня моделей, хотя возможно и иногда корректно объединять эти слоив единый объект.Слои модели, от самого высокого уровня до самого низкого, являются вашей моделью представления, вашей моделью домена и вашей моделью персистентности.Ваша модель представления должна точно описывать то, что на ваш взгляд, не больше и не меньше.Ваша доменная модель должна точно описывать вашу полную модель системы.Ваша модель персистентности должна точно описывать ваш метод хранения для ваших моделей домена.

ORM бывают разных форм и размеров, с различными концептуальными целями, и MongoDB , как вы его описываете - просто один из них,Иллюзия большинства из них обещает, что ваша модель постоянства должна совпадать с моделью вашего домена, а ORM - это всего лишь инструмент отображения из вашего хранилища данных в ваш объект домена.Это, безусловно, справедливо для простых сценариев, когда все ваши данные поступают из одного места, но в конечном итоге имеют свои ограничения, и ваше хранилище становится более прагматичным для вашей ситуации.Когда это происходит, модели имеют тенденцию становиться отличными.

При принятии решения о том, можете ли вы отделить модель своего домена от модели постоянства, следует придерживаться одного практического правила: легко ли вы поменять свое хранилище данных, не меняя модель своего домена.Если ответ «да», они могут быть объединены, в противном случае они должны быть отдельными моделями.Интерфейс репозитория естественным образом подходит для доставки моделей вашего домена из любого доступного хранилища данных.Некоторые из более новых облегченных ORM, такие как dapper и массовый , позволяют очень легко использовать модель вашего домена в качестве модели постоянства, поскольку они не требуют конкретной модели данных длячтобы выполнить постоянство, вы просто пишете запросы напрямую и позволяете ORM просто обрабатывать сопоставление.

Со стороны чтения модели представлений снова представляют собой отдельный уровень модели, поскольку они представляют собой подмножество вашей доменной модели, объединенной вместеОднако вам нужно для отображения информации на странице.Если вы хотите отобразить информацию о пользователе, со ссылками на всех его друзей и когда вы наведете указатель мыши на его имя, вы получите некоторую информацию об этом пользователе, ваша модель персистентности обрабатывать это напрямую, даже с MongoDB, скорее всего, будет довольно безумной.Конечно, не каждое приложение отображает такой набор взаимосвязанных данных в каждом представлении, и иногда модель домена - это именно то, что вы хотите отобразить.В этом случае нет причин добавлять дополнительный вес сопоставления от объекта, который имеет именно то, что вы хотите отобразить, к конкретной модели представления, имеющей те же свойства.В простых приложениях, если все, что я хочу сделать, это расширить модель домена, моя модель представления будет напрямую наследоваться от модели домена и добавлять дополнительные свойства, которые я хочу отобразить.Тем не менее, прежде чем ваше приложение MVC станет большим, я настоятельно рекомендую использовать модель представления для ваших макетов и сделать так, чтобы все модели представления на основе страниц наследовали от этой модели макета.

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

И наконец, о ваших точках:

  1. Меньше кода - это только преимущество, когда оно на самом деле более понятно.Читаемость и понятность этого - результаты навыков человека, пишущего это.Есть известные примеры короткого кода, который даже твердым разработчикам потребовалось много времени, чтобы разобраться и понять.Большинство из этих примеров взяты из грамотно написанного кода, который не более понятен.Более важным является то, что ваш код соответствует вашей спецификации на 100%.Если ваш код короткий, легко понятный и читаемый, но не соответствует спецификации, он бесполезен.Если это все эти вещи и соответствует спецификации, но легко использовать, спецификации и код не имеют смысла.

  2. Рефакторинг в считанные секунды безопаснорезультат хорошо написанного кода, а не его краткость.Следование принципу СУХОЙ сделает ваш код легко рефакторируемым, если ваша спецификация правильно соответствует вашим целям.В случае слоев модели модель вашего домена - это ключ к написанию хорошего, удобного в обслуживании и легкого рефакторинга кода.Модель вашего домена будет меняться с той скоростью, с которой меняются ваши бизнес-требования.Изменения в ваших бизнес-требованиях - это большие изменения, и нужно позаботиться о том, чтобы новая спецификация была полностью продумана, спроектирована, реализована, протестирована и т. Д. Например, сегодня вы говорите, что хотите добавить второй адрес электронной почты.Вам все равно придется изменить вид (если вы не используете какие-то леса).Кроме того, что, если завтра вы получите изменение требований для добавления поддержки до 100 адресов электронной почты?Первоначально предложенное вами изменение было довольно простым для любой системы, большие изменения требуют больше работы.

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