Изучая планы развития CMS Joomla, для написания одной из своих предыдущих статей (укр.), я наткнулся на аббревиатуру HMVC. Не сложно было понять, что это как-то связано со ставшим стандартом паттерном MVC. Найденная расшифровка: «HMVC — иерархические модель-вид-контроллер» — мало что объяснила. Дальнейшие поиски информации тоже дали не много, в основном теоретические рассуждения о паттерне и почти ничего о том, как же его использовать на практике. Однако немного поразмыслив, я понял, что уже использовал его в своем предыдущем проекте на Symfony 2. Более того, оказывается, что частично этот паттерн используют очень многие даже не догадываясь об этом.
Выглядящий в теории идеальным, MVC на практике сталкивается с рядом проблем. Для начала вспомним какую основную проблему он должен разрешить: разделив приложение на три различных аспекта (контроллер, вид и модель) добиться минимальной зависимости между ними, а также между различными частями приложения. В примерах из учебников с этим все в порядке. Есть модель чего-либо, вид для отображения данных и контроллер для осуществления действий в ответ на действия пользователя.

На практике обычно приходиться оперировать одновременно несколькими моделями, например: статьи, пользователи, комментарии. В принципе, это не критично, паттерн MVC это предусматривает, но это увеличивает количество зависимостей — вид и контроллер зависят более чем от одной модели, а от одной модели зависят более одного вида и контроллера.
Для отображения данных из разных моделей хотелось бы использовать виды созданные специально для них. Например, выглядит логичным отображение комментариев одинаково для статей и для товаров. Такое классический MVC не предусматривает, но это частично обходится использованием шаблонов. Т.е. используется один вид который получает данные из моделей, а для отображения их отображения используется комбинация нескольких шаблонов. И снова увеличивается количество зависимостей.
Иногда действия надо выполнить не над одной моделью, а над несколькими одновременно. Например, при удалении пользователя надо удалить все его статьи и комментарии. В результате приходится создавать контроллер, который описывает операции не только над моделью к которой он относится (в примере — пользователи), но над моделями к которым непосредственного отношения он не имеет. Таким образом появляются не очевидные зависимости.

Как же устранить эти проблемы? Поскольку проблемы возникают из-за того, что вместо MVC получается что-то наподобие MMMVVVCC, где каждая модель вид и контроллер могут принадлежать разным подсистемам, ответ очевиден — вернуться к MVC в котором есть лишь по одной модели, виду и контроллеру.
Итак, первый принцип HMVC: в приложении используются только жестко фиксированные триады модель-вид-контроллер, которые взаимодействуют с остальными подсистемы исключительно через контроллер.
Из этого выплывает второй принцип: для организации более сложных комбинаций используются иерархии триад.

На первый взгляд, может показаться, что для возможности реализации HMVC достаточно возможности вызова контроллера из другого контроллера. Но в веб-приложении поведение зависит не просто от команды переданной контроллеру, а от http-запроса в целом. И модель и вид могут сами анализировать запрос и некоторым образом изменять свое поведение. Поэтому требуется возможность передать запрос другому контроллеру, причем не обязательно тот же, что был получен. Сделать это можно тремя различными методами.
Самый простой способ отправить http-запрос — использовать для этого браузер. В этом случае даже не требуется какая-то особая поддержка фреймворком. И этот подход используется повсеместно — называется AJAX. Да-да, именно тот ajax. Мы может использовать одну базовую триаду модель-вид-контроллер для отображения основного содержимого страницы (например, текста статьи), которая остальные необходимые фрагменты (например, комментарии) получит при помощи ajax-запросов к таким-же триадам.

Такой подход позволяет для некоторых частей страницы использовать http-кеширование (кеширование данных в кеше браузера, прокси-сервера либо проксирующего http-сервера) а часть загружать в режиме real-time. Например страница статьи может быть загружена по частям следующим образом:
Плюсы:
Минусы:
Следующим по простоте реализации является отправка запроса веб-сервером самому себе. Для этого может использоваться curl, либо другая библиотека способная отправлять http-запросы. Этот подход похож на предыдущий, но с той разницей, что запросы отправляет не браузер пользователя, а сам веб-сервер в процессе формирования документа.
В качестве примера снова рассмотрим статью с комментариями. Основой страницы является статья, поэтому для ее отображения используются контроллер, вид и модель статьи. Но если в классическом MVC для отображения комментариев внутри вида статьи используется обращение к модели и виду комментариев, то HTMV предусматривает отправку запроса контроллеру комментариев. В данном случае посредством http-запроса, который инициирует запуск параллельного процесса, который выдаст готовый блок комментариев для статьи.

Как и в предыдущем случае, при таком подходе возможно использование http-кеширования, но для его использования необходимо использовать проксирующий http-сервер, например nginx.
Плюсы:
Минусы:
Под термином «внутри-серверный» я имею в виду, что все происходит внутри процесса веб-приложения. Как и в предыдущем случае, триады модель-вид-контроллер общаются между собой посредством запросов, и восприниматься они должны ими точно так же как обычные http-запросы, однако передачу этих запросов обеспечивает фреймворк. С точки зрения программиста, два последних варианта различаются между собой не существенно. В хорошем фреймворке разница должна быть лишь в одном параметре, который указывает должен ли подзапрос быть внутренним (внутри процесса), или внешним (вызывать настоящий http-запрос).

Плюсы:
Минусы:
Если веб-ресурс становиться достаточно популярным, может стать вопрос о том, что одного сервера для него недостаточно. И тогда встает вопрос о распределении нагрузки между несколькими серверами. Простейший вариант быстро распределить нагрузку — использовать несколько копий ресурса и репликацию базы данных. Но HMVC позволяет пойти другим путем — распределить между серверами задачи. Например, один сервер управляет статьями, один профилями пользователей, а третий комментариями. В случае достаточной изолированности модулей, для быстрой реализации этого достаточно лишь прописать в соответствующих запросах что они внешние, и указать адреса серверов для их обработки.
Создавая реальное веб-приложение вовсе не обязательно ограничиваться каким-то одним из вариантов реализации HMVC. В каждом конкретном случае можно выбирать тот, что лучше всего подходит именно для него. А варианты «сервер-серверный» и «внутри-серверный» вообще можно переключать «на ходу».
Проблемы MVC
Выглядящий в теории идеальным, MVC на практике сталкивается с рядом проблем. Для начала вспомним какую основную проблему он должен разрешить: разделив приложение на три различных аспекта (контроллер, вид и модель) добиться минимальной зависимости между ними, а также между различными частями приложения. В примерах из учебников с этим все в порядке. Есть модель чего-либо, вид для отображения данных и контроллер для осуществления действий в ответ на действия пользователя.

Проблема первая
На практике обычно приходиться оперировать одновременно несколькими моделями, например: статьи, пользователи, комментарии. В принципе, это не критично, паттерн MVC это предусматривает, но это увеличивает количество зависимостей — вид и контроллер зависят более чем от одной модели, а от одной модели зависят более одного вида и контроллера.
Проблема вторая
Для отображения данных из разных моделей хотелось бы использовать виды созданные специально для них. Например, выглядит логичным отображение комментариев одинаково для статей и для товаров. Такое классический MVC не предусматривает, но это частично обходится использованием шаблонов. Т.е. используется один вид который получает данные из моделей, а для отображения их отображения используется комбинация нескольких шаблонов. И снова увеличивается количество зависимостей.
Проблема третья
Иногда действия надо выполнить не над одной моделью, а над несколькими одновременно. Например, при удалении пользователя надо удалить все его статьи и комментарии. В результате приходится создавать контроллер, который описывает операции не только над моделью к которой он относится (в примере — пользователи), но над моделями к которым непосредственного отношения он не имеет. Таким образом появляются не очевидные зависимости.

Основная идея HMVC
Как же устранить эти проблемы? Поскольку проблемы возникают из-за того, что вместо MVC получается что-то наподобие MMMVVVCC, где каждая модель вид и контроллер могут принадлежать разным подсистемам, ответ очевиден — вернуться к MVC в котором есть лишь по одной модели, виду и контроллеру.
Итак, первый принцип HMVC: в приложении используются только жестко фиксированные триады модель-вид-контроллер, которые взаимодействуют с остальными подсистемы исключительно через контроллер.
Из этого выплывает второй принцип: для организации более сложных комбинаций используются иерархии триад.

На первый взгляд, может показаться, что для возможности реализации HMVC достаточно возможности вызова контроллера из другого контроллера. Но в веб-приложении поведение зависит не просто от команды переданной контроллеру, а от http-запроса в целом. И модель и вид могут сами анализировать запрос и некоторым образом изменять свое поведение. Поэтому требуется возможность передать запрос другому контроллеру, причем не обязательно тот же, что был получен. Сделать это можно тремя различными методами.
Клиент-серверный HMVC
Самый простой способ отправить http-запрос — использовать для этого браузер. В этом случае даже не требуется какая-то особая поддержка фреймворком. И этот подход используется повсеместно — называется AJAX. Да-да, именно тот ajax. Мы может использовать одну базовую триаду модель-вид-контроллер для отображения основного содержимого страницы (например, текста статьи), которая остальные необходимые фрагменты (например, комментарии) получит при помощи ajax-запросов к таким-же триадам.

Такой подход позволяет для некоторых частей страницы использовать http-кеширование (кеширование данных в кеше браузера, прокси-сервера либо проксирующего http-сервера) а часть загружать в режиме real-time. Например страница статьи может быть загружена по частям следующим образом:
- сама страница с текстом статьи статична и по возможности извлекается из кеша браузера, в котором может храниться довольно долго;
- комментарии постоянно обновляются и поэтому не кешируются и каждый раз запрашиваются с веб-сервера;
- блок последних новостей обновляется время от времени, может извлекается из кеша, но храниться в нем не так долго как статья.
Плюсы:
- не нужна поддержка фреймворком (можно вообще без него обойтись);
- возможность гибкого http-кеширования;
- возможность отправить запрос на другой веб-сервер, распределив таким образом нагрузку между несколькими серверами.
Минусы:
- необходимо писать java-скрипты;
- увеличивается количество запросов от браузера серверу, что может увеличить время загрузки страницы и нагрузку на веб-сервер.
Сервер-серверный HMVC
Следующим по простоте реализации является отправка запроса веб-сервером самому себе. Для этого может использоваться curl, либо другая библиотека способная отправлять http-запросы. Этот подход похож на предыдущий, но с той разницей, что запросы отправляет не браузер пользователя, а сам веб-сервер в процессе формирования документа.
В качестве примера снова рассмотрим статью с комментариями. Основой страницы является статья, поэтому для ее отображения используются контроллер, вид и модель статьи. Но если в классическом MVC для отображения комментариев внутри вида статьи используется обращение к модели и виду комментариев, то HTMV предусматривает отправку запроса контроллеру комментариев. В данном случае посредством http-запроса, который инициирует запуск параллельного процесса, который выдаст готовый блок комментариев для статьи.

Как и в предыдущем случае, при таком подходе возможно использование http-кеширования, но для его использования необходимо использовать проксирующий http-сервер, например nginx.
Плюсы:
- клиенту отдается готовая страница;
- возможность гибкого http-кеширования;
- возможность отправить запрос на другой веб-сервер, распределив таким образом нагрузку между несколькими серверами.
Минусы:
- для формирования одной страницы запускается несколько параллельных процессов, что увеличивает нагрузку на веб-сервер.
Внутри-серверный HMVC
Под термином «внутри-серверный» я имею в виду, что все происходит внутри процесса веб-приложения. Как и в предыдущем случае, триады модель-вид-контроллер общаются между собой посредством запросов, и восприниматься они должны ими точно так же как обычные http-запросы, однако передачу этих запросов обеспечивает фреймворк. С точки зрения программиста, два последних варианта различаются между собой не существенно. В хорошем фреймворке разница должна быть лишь в одном параметре, который указывает должен ли подзапрос быть внутренним (внутри процесса), или внешним (вызывать настоящий http-запрос).

Плюсы:
- нет необходимости в параллельном запуске экземпляра веб-приложения.
Минусы:
- нет возможности использовать http-кеширование.
Масштабирование HMVC-приложения
Если веб-ресурс становиться достаточно популярным, может стать вопрос о том, что одного сервера для него недостаточно. И тогда встает вопрос о распределении нагрузки между несколькими серверами. Простейший вариант быстро распределить нагрузку — использовать несколько копий ресурса и репликацию базы данных. Но HMVC позволяет пойти другим путем — распределить между серверами задачи. Например, один сервер управляет статьями, один профилями пользователей, а третий комментариями. В случае достаточной изолированности модулей, для быстрой реализации этого достаточно лишь прописать в соответствующих запросах что они внешние, и указать адреса серверов для их обработки.
Напоследок
Создавая реальное веб-приложение вовсе не обязательно ограничиваться каким-то одним из вариантов реализации HMVC. В каждом конкретном случае можно выбирать тот, что лучше всего подходит именно для него. А варианты «сервер-серверный» и «внутри-серверный» вообще можно переключать «на ходу».