Почему не рекомендуется передавать сущности как модели в MVC?

Мы разрабатываем довольно большое приложение с помощью MVC 2 RC2 и получили отзывы о том, как мы используем отложенную загрузку Entity Framework.

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

Можете ли вы помочь нам понять эту проблему дизайна?

Спасибо!


person sabanito    schedule 24.02.2010    source источник


Ответы (4)


Основная проблема здесь — сцепление. Идея модели, которая является буквой "M" в "MVC", заключается в том, что она не имеет внешних зависимостей. Это «ядро» вашего приложения. Дерево зависимостей хорошо спроектированной архитектуры приложения должно выглядеть примерно так:

                       +---------------------------------------> Views
                       |                                           |
                       |                                           |
                       |                                           v
                  Controllers ----+-> Model Transformer -----> View Model
                       |           \         |
                       |            \        |
                       v             \       v
Data Access <---- Persistence --------> Domain Model
                       |             /
                       |            /
                       v           /
                     Mapper ------+

Теперь я понимаю, что не совсем убедительно просто сказать «вот архитектура, это то, что вы должны использовать», поэтому позвольте мне объяснить, что здесь происходит:

  1. Контроллер получает запрос.
  2. Контроллер обращается к какому-то уровню сохранения (например, репозиторию).
  3. Уровень сохраняемости извлекает данные, а затем использует преобразователь для сопоставления с моделью предметной области.
  4. Контроллер использует преобразователь для преобразования модели предметной области в модель представления.
  5. Контроллер выбирает необходимое представление и применяет к нему модель представления.

Итак, почему это хорошо?

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

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

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

  • Представления зависят от отдельной модели пользовательского интерфейса. Это изолирует их от изменений в модели предметной области. Это означает, что если ваша бизнес-логика изменится, вам не нужно менять каждое представление в вашем проекте. Это позволяет представлениям быть «тупыми», какими и должны быть представления — они не более чем заполнители для данных представления. Это также означает, что должно быть просто воссоздать представление, используя другой тип пользовательского интерфейса, т. е. интеллектуальное клиентское приложение, или переключиться на другой механизм представления (Spark, NHaml и т. д.).


Теперь при использовании O/R Mappers, таких как Linq to SQL или Entity Framework, очень возникает соблазн рассматривать классы, которые они генерируют, как модель предметной области. Конечно, это выглядит как модель предметной области, но это не так. Почему?

  • Классы сущностей привязаны к вашей реляционной модели, которая со временем может и будет значительно отличаться от вашей предметной модели;

  • Классы сущностей тупые. Трудно поддерживать какие-либо сложные сценарии проверки или интегрировать какие-либо бизнес-правила. Это называется анемичной моделью предметной области.

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

  • Но самое главное, "модель предметной области" больше не является независимой. Она не может существовать вне любой сборки, имеющей логику доступа к данным. Ну, вроде как можно, есть способы добиться этого, если вы действительно потрудитесь над этим, но это не так, как делает это большинство людей, и даже если вы это сделаете, вы обнаружите, что фактический дизайн модели предметной области ограничивается вашей реляционной моделью и, в частности, тем, как ведет себя EF. Суть в том, что если вы решите изменить свою модель постоянства, вы сломаете модель предметной области, а ваша модель предметной области является основой почти для всего остального в вашем приложении. .

Классы Entity Framework не являются моделью предметной области. Они являются частью реляционной модели данных и имеют такие же или похожие имена, которые вы могли бы дать классам в модели предметной области. Но они совершенно разные с точки зрения управления зависимостями. Использование классов, созданных с помощью инструмента ORM, в качестве модели вашей предметной области может привести только к чрезвычайно хрупкой архитектуре/дизайну; каждое изменение, которое вы вносите почти в любую часть приложения, будет иметь множество предсказуемых и непредсказуемых каскадных эффектов.

Кажется, есть много людей, которые думают, что вам не нужна сплоченная, независимая модель предметной области. Обычно оправданием является то, что (а) это небольшой проект и/или (б) их модель предметной области на самом деле не имеет никакого поведения. Но небольшие проекты становятся большими, а бизнес-правила (намного) усложняются, а анемичная или несуществующая модель предметной области — это не то, что можно просто убрать с помощью рефакторинга.

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

person Aaronaught    schedule 25.02.2010
comment
Неправильно, что EF привязывает вас к реляционной модели, сгенерированные классы привязывают вас к концептуальной модели, а на v3.5 еще и к их API, но v4.0 поддерживает POCO. Эту модель можно использовать в приложении, дополнительная не нужна, для этого и предназначен EF. - person Max Toro; 25.02.2010
comment
@Max Toro: То, что это POCO, не делает его моделью предметной области. Вам все еще нужно спроектировать его на основе реляционной модели, чтобы он соответствовал. Покажите мне пример сопоставления EF, где сопоставленная модель существенно отличается от реляционной. - person Aaronaught; 25.02.2010
comment
@Aaronaught У меня нет большого опыта работы с EF, но предполагается, что он поддерживает множество сценариев сопоставления, таких как наследование, сложные свойства (например, 2 столбца сопоставляются с 1 свойством). Наиболее важным для меня является удаление внешних ключей из ваших классов. Изменений много, поэтому у него есть собственный язык запросов, ESQL. - person Max Toro; 25.02.2010
comment
@Max Toro: Даже если вы могли выполнить идеальное сопоставление с реальной моделью предметной области, используя EF (а EF еще нигде не достиг этого уровня, даже NHibernate не идеален), это не устраняет скрытые зависимости , что является серьезной проблемой. Ленивая загрузка может привести к ошибкам; нетерпеливая загрузка может привести к серьезным проблемам с производительностью. Должен существовать уровень сопоставления предметной области, который решает, что делать, в зависимости от контекста, в котором он используется. - person Aaronaught; 25.02.2010
comment
@Aaronaught Вы имеете в виду стратегию загрузки, основанную на потребностях контроллера? Я согласен, что это намного лучше, чем ленивая загрузка в представлении. Этого можно добиться, отключив ленивую загрузку. - person Max Toro; 25.02.2010
comment
@Max Toro: Простое отключение ленивой загрузки по всем направлениям убьет производительность. Наиболее эффективные запросы для больших наборов данных обычно включают возврат нескольких наборов результатов и фактическое использование идентификаторов FK (то, что EF пытается скрыть от вас) для их объединения на уровне сопоставления. Это не та логика, которая должна быть в контроллере; он должен находиться на уровне интеллектуального отображения запросов (т. е. в репозитории). Это всего лишь одна из многих областей, где сильная зависимость от ORM начинает разрушаться; Я лично испытал десятки, может быть, сотни. - person Aaronaught; 25.02.2010
comment
@Aaronaught Почему отключение ленивой загрузки снижает производительность? Я не говорю, что жадно загружайте все, только то, что вам нужно. Да и вообще, тема не о производительности, а о муфте. Я согласен, что необходим сервисный уровень, который, на мой взгляд, может возвращать ту же модель, сгенерированную EF. - person Max Toro; 25.02.2010
comment
Нет, если модель была сгенерирована с помощью EF, она страдает от всех проблем, которые я первоначально описал. Если у вас сложное сопоставление с классами POCO, вы можете смягчить некоторые (но не все) эти проблемы. - person Aaronaught; 25.02.2010
comment
@Aaronaught Извините, я имел в виду модель POCO. Я понимаю проблему сцепления, но не проблему загрузки, что не так с нетерпеливой загрузкой? - person Max Toro; 25.02.2010
comment
@Max Toro: целевая нетерпеливая загрузка, когда вы указываете точно, какие ассоциации должны быть нетерпеливо извлечены для конкретного запроса, в порядке. Страстная загрузка всех сущностей в графе была бы катастрофой; вы бы закончили JOIN почти каждой таблицей в базе данных. - person Aaronaught; 25.02.2010
comment
@Aaronaught Конечно, нетерпеливая загрузка, основанная на стратегии. Таким образом, проблемы заключаются в отображении, соединении и загрузке. Я думаю, что EF (по крайней мере, v4) предлагает достойное решение для всех трех. - person Max Toro; 25.02.2010
comment
@Max Toro: EF 4 не предлагает полного решения. В нем есть большинство крючков, необходимых для создания законченного решения, но не без еще одного уровня, лежащего поверх него, который решает, как создать правильную модель предметной области для конкретного сценария. В качестве альтернативы, эта логика должна быть в вашем пользовательском интерфейсе/контроллере, и вы определенно не хотите, чтобы она была там. - person Aaronaught; 25.02.2010
comment
@Aaronaught Вау, спасибо за невероятно подробный ответ. Я думаю, что понял суть связи, но не слишком ли много работы по созданию классов, которые просто имитируют потребности вашего пользовательского интерфейса? Используете ли вы что-то вроде Automapper или что-то, что преобразует ваши классы Poco/Entity в классы пользовательского интерфейса? Я определенно согласен и заплатил бы цену времени за сложные представления, но бывают случаи, когда ваше представление точно показывает, что ваш POCO/сущность имеет в своих свойствах (записи каталога) - person sabanito; 25.02.2010
comment
@sabanito: может показаться, что это много работы, но если учесть типичную разбивку работы по разработке (более 80% которой приходится на проектирование, рефакторинг и отладку), на самом деле это не так, тем более что EF может генерировать классы сущностей. для вас - вам нужно только написать домен и модели просмотра, которые вам в конечном итоге все равно нужно будет написать. AutoMapper — отличный инструмент, который можно использовать, когда какая-то часть вашей предметной модели оказывается идентичной реляционной модели; это позволяет вам быстро начать работу, но у вас все еще есть возможность изменить сопоставление позже. - person Aaronaught; 25.02.2010
comment
@Aaronaught Конечно, необходим сервисный уровень, контроллеры не должны напрямую вызывать ObjectContext. Я хочу сказать, что в EF есть все, что вам нужно, поэтому вы можете иметь одну модель (домен), а не две модели (реляционную и доменную). В противном случае нет смысла использовать такой продвинутый/сложный продукт, как EF. - person Max Toro; 25.02.2010
comment
@Max Toro: По большей части я согласен, но теперь у вас есть один из двух сценариев. Либо (а) ваш сервисный слой и дизайн EF сопоставляются с классами моделей POCO в отдельной сборке, и в этом случае вы просто создали свою собственную модель предметной области, либо (б) ваш сервисный уровень возвращает классы сущностей в той же сборке, что и ObjectContext, что означает, что любой потребитель сервисного уровня берет на себя дополнительную зависимость от сборки EF, что является одной из основных проблем, о которых я указывал выше. - person Aaronaught; 25.02.2010
comment
Жаль, что я могу проголосовать за этот ответ только один раз! Было бы неплохо увидеть полный пример того, как вы будете проектировать/реализовывать такую ​​систему (особенно части EF/DomainModel и сопоставление между ними). - person M4N; 28.02.2010
comment
Хотел бы я проголосовать за этот ответ еще как минимум два или три раза. - person Brant Bobby; 17.03.2010

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

person mmx    schedule 24.02.2010
comment
Можно ли оправдать такой дизайн, если в представлении отображается много данных, которые при быстрой загрузке могут привести к тому, что EF (или любой ORM) будет генерировать УЖАСНЫЙ и медленный SQL? Так было со многими нашими взглядами... - person sabanito; 25.02.2010
comment
@sabanito: В реальном мире каждый дизайн может быть оправдан, если вы добавите к нему факторы стоимости, времени и ресурсов! Для этого могут быть лучшие способы (создание более конкретных ViewModels для ваших представлений), но это может добавить слишком много кода и может оказаться неосуществимым в ваших обстоятельствах. Все, что вы можете сказать, это то, что, учитывая бесконечные ресурсы, это, вероятно, не лучший способ добиться цели. - person mmx; 25.02.2010
comment
@sabanito: Если EF генерирует УЖАСНЫЙ и медленный SQL, это только ваша вина. Не пытайтесь загрузить все в одном запросе, используя Include, а разделите его на несколько, чтобы отношения 1 к n не загружались в одном операторе sql. Стремительная загрузка не делает ее медленнее, особенно когда вам нужны все данные, а вы по-прежнему загружаете все с отложенной загрузкой. - person LukLed; 25.02.2010

Проблема в том, что ваш пользовательский интерфейс более тесно связан с вашей сущностью, чем это необходимо.

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

person womp    schedule 24.02.2010
comment
Мы использовали некоторые ViewModels, но только в представлениях, которые объединяют данные из разных сущностей. Что вы имеете в виду под конструкциями ViewData? - person sabanito; 25.02.2010
comment
Я просто имею в виду передачу сложных объектов в ViewData, на которые ссылаются магические строки... обычно контролируемые какой-то доморощенной структурой, чтобы отслеживать их все. - person womp; 25.02.2010

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

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

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

person Luhmann    schedule 24.02.2010
comment
То есть вы имеете в виду, что каждое представление должно получать класс ViewModel в своем свойстве модели? Даже если это довольно просто? - person sabanito; 25.02.2010
comment
Я определенно предпочитаю это. Это последовательно, и проблемы хорошо разделены. - person Luhmann; 25.02.2010