Знакомство с архитектурой MVC

Самая популярная архитектура приложений, о которой знает каждый программист, — это MVC. MVC расшифровывается как Model-View-Controller.

Это не столько архитектура приложений, как архитектура компонентов приложения, но к этому нюансу вернемся попозже. Что же такое MVC?

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

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

Эту модель придумали еще в 1978 (!) году. Да, проблемы с правильной архитектурой ПО были актуальны еще 50 лет назад. Вот как эта модель описывается диаграммой в оригинале:

Знакомство с архитектурой MVC

Модель предоставляет данные и методы работы с ними: запросы в базу данных, проверку на корректность. Модель не зависит от представления (не знает как данные визуализировать) и контроллера (не имеет точек взаимодействия с пользователем), предоставляя доступ к данным и управлению ими.

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

Представление отвечает за получение необходимых данных из модели и отправляет их пользователю. Представление не обрабатывает введенные данные пользователя.

Контроллер обеспечивает “связь” между пользователем и системой. Контролирует и направляет данные от пользователя к системе и наоборот. Использует модель и представление для реализации необходимого действия.

Определенная сложность есть с тем, что данная модель за десятки лет немного эволюционировала. То есть название осталось тем же, а назначение частей начало меняться.

Архитектура MVC в вебе

Идея, которая лежит в основе конструкционного шаблона MVC, очень проста: нужно четко разделять ответственность за различное функционирование в наших приложениях:

Model — обработка данных и логика приложения.

View — предоставления данных пользователю в любом поддерживаемом формате.

Controller — обработка запросов пользователя и вызов соответствующих ресурсов.

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

Контроллер (Controller)

Пользователь кликает на различные элементы на странице в браузере, в результате чего браузер отправляет различные HTTP запросы: GET, POST или другие. К контроллеру можно отнести браузер и JS-код, которые работают внутри страницы.

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

Модель (Model)

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

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

Дальше идут наборы правил: что можно делать, что нельзя, какие наборы данных допустимы, а какие нет. Может ли книга быть без автора? А автор без книг? Может ли дата рождения пользователя быть в 300 году и тому подобное.

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

Модель содержит наиболее важную часть логики нашего приложения, логику, которая решает задачу, с которой мы имеем дело (форум, магазин, банк и тому подобное). Контроллер содержит в основном организационную логику для самого приложения (прямо как твой Project Manager).

Вид (View)

View обеспечивает различные способы представления данных, которые получены из модели. Он может быть шаблоном, который заполняется данными. Может быть несколько различных views и контроллер выбирает, какой подходит наилучшим образом для текущей ситуации.

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

Пример MVC в вебе

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

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

Также тебе нужны наборы views: список книг, информация об одной книге, корзина, список заказов. Каждая страница веб-приложения — это фактически и есть отдельный view, который отображает пользователю определенный аспект модели.

Давай посмотрим, что произойдет, если пользователь откроет список рекомендованных магазином книг для просмотра названий. Всю последовательность действий можно описать в виде 6 шагов:

Пример MVC в вебе

Шаги:

  1. Пользователь кликает по ссылке «рекомендованы» и браузер отправляет запрос на, допустим, /books/recommendations.
  2. Контроллер проверяет запрос: пользователь должен быть залогинен. Или у нас должны быть подборки книг для незалогиненных пользователей. Затем контроллер обращается к модели и просит ее отдать список книг, рекомендованных пользователю N.
  3. Модель обращается в базу данных, достает оттуда информацию о книгах: популярные сейчас книги, книги, купленные пользователем, книги, купленные его друзьями, книги из его wish list. На основе этих данных модель строит список из 10 рекомендованных книг и возвращает их контроллеру.
  4. Контроллер получает список рекомендованных книг и смотрит на него. На этом этапе контроллер принимает решения! Если книг мало или список вообще пустой, то он запрашивает список книг для незалогиненного пользователя. Если сейчас идет акция, то контроллер может добавить в список акционные книги.
  5. Контроллер определяется с тем, какую страницу показать пользователю. Это может быть страница с ошибкой, страница со списком книг, страница поздравление о том, что пользователь стал миллионным посетителем.
  6. Сервер отдает клиенту страницу (view), выбранную контроллером. Она заполняется нужными данными (имя пользователя, список книг) и уходит к клиенту.
  7. Клиент получает страницу и отображает ее пользователю.

В чем преимущества такого подхода?

Самое очевидное преимущество, которое мы получаем от использования концепции MVC — это четкое разделение логики представления (интерфейса пользователя) и логики приложения (серверная часть).

Второе преимущество — это разделение серверной части на две: умная модель (исполнитель) и контроллер (центр принятия решений).

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

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

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

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

Понимая концепцию MVC, ты как разработчик осознаешь, где нужно добавить сортировку списка книг:

  • На уровне запроса к базе данных.
  • На уровне бизнес-логики (модели).
  • На уровне бизнес-логики (контроллер).
  • В представлении — на стороне клиента.

И это не риторический вопрос. Вот прямо сейчас и подумай: где и почему нужно добавить код по сортировке списка книг.

Классическая модель MVC

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

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

Примерно вот так выглядит взаимодействие компонентов в различных моделях:

Классическая модель MVC

В мобильных приложениях (активная модель) активно используются события и механизм подписки на события. При таком подходе view (вид) подписывается на изменения модели. Затем, когда происходит какое-то событие (например, пользователь нажимает кнопку), вызывается контроллер. Он дает и модели команду на изменение данных.

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

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

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

В нынешнее время эта проблема частично решена с помощью таких подходов:

  • Регулярный опрос сервера на счет изменения важных данных (раз в минуту или чаще).
  • WebSocket’ы позволяют клиентку подписываться на сообщения сервера.
  • Web-push-уведомления со стороны сервера.
  • Протокол HTTP/2 позволяет серверу инициировать отправку сообщений клиенту.