MVC (Model-View-Controller) — архитектурный шаблон, который делит приложение на три группы компонентов: модель, представление и контроллер. MDN описывает его как способ разделить бизнес-логику и отображение, чтобы упростить поддержку кода. Модель управляет данными и правилами, представление отвечает за вывод, контроллер направляет команды между ними.
Расшифровка MVC и роли трёх компонентов
Аббревиатура MVC раскрывается как Model, View, Controller. Это три группы ответственности. Microsoft в обзоре ASP.NET Core MVC пишет, что шаблон разделяет приложение на модели, представления и контроллеры и помогает достичь разделения ответственности. Пользовательский запрос попадает в контроллер, тот работает с моделью, выбирает представление и передаёт ему нужные данные.
Модель. MDN определяет модель как компонент, который управляет данными и бизнес-логикой. В обзоре Microsoft модель представляет состояние приложения и бизнес-логику, а логика хранения состояния инкапсулируется в модели. Модель описывает, какие данные содержит приложение и какие правила к ним применяются.
Модель не равна таблице базы данных. Таблица — один из способов хранить состояние, но сама модель — это правила предметной области: какие поля допустимы, какие переходы статуса возможны, что считается корректной записью. В обзоре Microsoft бизнес-логика помещается в модель: контроллер не должен обрастать лишними обязанностями. Хранение данных при этом может быть вынесено в отдельный слой — репозиторий, который выполняет параметризованные запросы. Тогда модель остаётся про правила, а репозиторий — про доступ к хранилищу.
Представление. MDN: представление определяет, как данные приложения должны отображаться. Microsoft: представления отвечают за показ контента через интерфейс, логики внутри них минимум, и она относится к отображению. Представление получает готовые данные и превращает их в разметку.
Контроллер. MDN: контроллер содержит логику, которая обновляет модель и представление в ответ на ввод пользователя. Microsoft: контроллеры обрабатывают взаимодействие, работают с моделью и выбирают представление для отрисовки; контроллер — начальная точка входа и отвечает за выбор типов модели и представления. Контроллер разбирает запрос, вызывает модель, решает, что показать, и формирует ответ.
| Компонент | Что делает | Чего в нём нет |
|---|---|---|
| Модель | Правила предметной области, состояние | Разметки и разбора URL |
| Представление | Показ готовых данных | Запросов к базе |
| Контроллер | Разбор запроса, выбор модели и представления | Придуманных цен и SQL |
| Репозиторий | Параметризованные запросы к хранилищу | Бизнес-правил |
Таблица показывает границу: разбор пути и HTTP-статуса живёт в контроллере, доменные правила — в модели, параметризованный SQL — в репозитории под моделью, готовые данные, экранирование и подписи — в представлении.
Полный псевдокод: запрос карточки ремонта
Разберём учебный пример. Это framework-neutral псевдокод. Вход: GET /repairs/41, запись {id:41, item:"чайник", status:"ready", price:800}. Договор отображения: статус ready превращается в «готово», метка валюты выбрана «руб.». Пример описывает логику.
Repository.find(id):
выбрать строку repairs по параметру :id
вернуть запись или None
RepairService.get(id):
row = Repository.find(id)
если row is None: вернуть None
проверить доменные поля row
вернуть Repair(row.id, row.item, row.status, row.price)
Controller.detail(path_id):
id = parse_integer(path_id)
record = RepairService.get(id)
если record is None: вернуть HTTP(404, "Ремонт не найден")
вернуть HTTP(200, View.card(record))
View.card(record):
labels = {"ready": "готово"}
вернуть "Ремонт " + escape(record.id) + ": " + escape(record.item)
+ ", " + escape(labels[record.status]) + ", " + escape(record.price) + " руб."
Здесь escape обозначает HTML-экранирование в выбранном шаблонизаторе. Порядок вызовов задаёт маршрут запроса: контроллер разбирает путь, сервис достаёт запись через репозиторий, представление собирает строку. Каждый слой знает только свою часть работы.
Эталон для записи 41: HTTP 200, текст «Ремонт 41: чайник, готово, 800 руб.». Представление переводит статус по словарю подписей и подставляет цену из записи. Контроллер не вычисляет цену и не знает про словарь подписей — он передаёт запись дальше.
Дальше разберём границы: что происходит с несуществующей записью и как проверить, что слои не смешались.
Эталон 200 и 404: три проверки границ
Для несуществующего идентификатора 42 сервис возвращает None, контроллер формирует HTTP 404 без вызова View.card. Представление не запускается, потому что показывать нечего. Это и есть граница: отсутствие данных обрабатывается до отрисовки.
Три проверки, которые показывают, что ответственности разошлись правильно.
Проверка 1. Запись 41 отображает именно переведённый статус и цену 800. Если в ответе появилось «ready» вместо «готово», значит перевод статуса уехал не в тот слой. Подпись живёт в представлении, и её место там.
Проверка 2. Запись 42 не вызывает View.card и даёт 404. Если представление всё-таки вызвалось, значит контроллер не проверил результат сервиса. Проверка на None стоит до выбора представления.
Проверка 3. В представлении нет SQL, а контроллер не придумывает цену. Если в шаблоне появился запрос к базе, доступ к данным просочился в отображение. Если контроллер сам считает стоимость, бизнес-правило уехало из модели.
| Ситуация | Ответ | Что вызвано |
|---|---|---|
| GET /repairs/41 | HTTP 200 | View.card с записью |
| GET /repairs/42 | HTTP 404 | Ответ без View.card |
| Статус ready | «готово» | Словарь подписей в представлении |
| Цена 800 | 800 руб. | Значение из записи |
Таблица фиксирует эталоны. Эти проверки ловят типовую ошибку: когда разбор запроса, правила и отображение смешаны в одном месте, изменение подписи ломает логику, а правка правила меняет разметку. Microsoft прямо предупреждает: если код представления и бизнес-логика объединены в одном объекте, то при каждой правке интерфейса приходится менять объект с бизнес-логикой, что вносит ошибки и требует перепроверки логики.
Терминология Django: view, template и controller
Названия компонентов в разных фреймворках расходятся. Официальный FAQ Django объясняет, почему проект использует свою схему. Django называет свою раскладку MTV — model, template, view.
В трактовке Django представление (view) определяет, какие данные нужно показать пользователю. Представление — это Python-функция-обработчик для конкретного URL, потому что она определяет, какие данные подаются. Отделение содержания от подачи выполняет template: view описывает, какие данные представить, а template — как их показать.
Где же контроллер? FAQ Django относит роль controller скорее к самому фреймворку: это machinery, которая по конфигурации URL отправляет запрос нужному view. Обычная Django view не объявляется однозначным классическим MVC-контроллером. Поэтому раскладка нашего псевдокода не переносится механически на названия Django: то, что в примере называется контроллером, в терминах Django ближе к URL-механике и выбору view.
| Термин | В нашем примере | В терминологии Django |
|---|---|---|
| Model | Правила и состояние | Model |
| View (данные) | Контроллер выбирает данные | View — какие данные |
| View (показ) | Представление | Template — как показать |
| Controller | Разбор запроса | Framework и URL machinery |
Microsoft и MDN дают свои примеры и своё разделение ролей. MDN описывает модель как управление данными и логикой, представление как разметку и показ, контроллер как маршрутизацию команд. В обзоре Microsoft контроллер — точка входа, которая выбирает модель и представление. Rails в руководстве для начинающих показывает собственный пример разделения. Один шаблон, разные имена.
Как читать этот разбор и где чаще всего ошибаются
Проверку удобно вести от ожидаемого ответа к коду. Сначала зафиксируйте, что должно получиться для записи 41: код 200 и строка с переведённым статусом и ценой. Затем пройдите маршрут по шагам и на каждом спросите, кто отвечает за этот шаг. Если ответ «никто конкретный» или «сразу двое», граница размыта.
Типичная ошибка новичка — перенести перевод статуса в контроллер или в сервис. Внешне ответ совпадёт, но при добавлении нового статуса правки расползутся по нескольким файлам, а представление начнёт зависеть от доменных значений. Вторая частая ошибка — проверять существование записи уже внутри представления. Тогда для идентификатора 42 отрисовка всё равно запустится, и вместо чистого 404 появится пустая или сломанная карточка.
Действие читателя простое: возьмите свой текущий обработчик одного запроса и подпишите рядом с каждой строкой, к какому слою она относится. Строки, которые не удаётся отнести однозначно, и есть места смешения. Перенесите разбор пути и статус в контроллер, правила и проверки полей — в модель или сервис, доступ к хранилищу — в репозиторий, подписи и экранирование — в представление. После правки повторите три проверки из предыдущего раздела: они покажут, что границы восстановлены.
Частые вопросы
MVC что это простыми словами?
Это способ разложить приложение на три части: правила работы с данными, показ результата и обработку запроса. MDN описывает шаблон как разделение бизнес-логики и отображения. Каждая часть получает одну задачу, поэтому код проще менять и тестировать.
Модель в MVC — это таблица базы данных?
Нет. Модель — это правила предметной области и состояние приложения. Microsoft помещает бизнес-логику и логику хранения состояния в модель. Таблица базы данных — лишь один из способов хранить состояние, а доступ к нему может быть вынесен в отдельный репозиторий.
Чем контроллер отличается от представления?
Контроллер разбирает запрос, вызывает модель и выбирает, что показать. Представление получает готовые данные и строит разметку. Microsoft пишет, что представление только показывает информацию, а контроллер обрабатывает ввод и взаимодействие. В нашем примере контроллер формирует HTTP-статус, представление собирает строку карточки.
Почему в Django view называется не так, как в MVC?
Django использует схему MTV: model, template, view. В трактовке Django view описывает, какие данные представить, а template — как их показать. Роль controller FAQ Django относит скорее к самому фреймворку и URL-механике, выбирающей view. Поэтому имена расходятся, хотя идея разделения сохраняется.
Как проверить, что слои MVC не смешались?
Возьмите два запроса: существующий и несуществующий идентификатор. Существующий должен вернуть 200 с переведённым статусом и ценой из записи, несуществующий — 404 без вызова представления. Затем проверьте, что в представлении нет SQL, а контроллер не вычисляет бизнес-значения. Эти три проверки показывают границы ответственности.
