HMVC — различия между версиями
м |
м |
||
Строка 1: | Строка 1: | ||
− | Об одном правильном подходе к HMVC (Hierarchical MVC). | + | Об одном правильном подходе к HMVC (Hierarchical MVC) и кэшированию HTML на стороне сервера. |
+ | |||
+ | Делим страницу на блоки, каждый блок обрабатывается одним контроллером. При этом контроллеры образуются в "иерархию" - каждый блок может вызывать другие и подставлять внутрь себя их вывод. Каждый вызов контроллера описывается именем контроллера и параметрами, в которых, аналогично URL-параметрам, допускаются только сериализованные данные, а не объекты. | ||
+ | |||
+ | Для корректной инвалидации кэша используем теги. | ||
+ | |||
+ | Далее запуск каждого контроллера делится на два метода: | ||
+ | * check() - запускается всегда, перед попыткой загрузить результат из кэша. Здесь должны выполняться следующие задачи: | ||
+ | ** обработка запросов на модификацию данных (и редирект после сохранения изменений) | ||
+ | ** валидация параметров запроса, редиректы на очищенные URL | ||
+ | ** вычисление ключа кэша | ||
+ | ** если хочется, вызов будущих подзапросов (метод subrequest()). это бывает полезно, если не хочется множить ключи кэша в зависимости от набора подзапросов. в то же время, это бывает вредно, если для вызова подзапросов нужно совершить дополнительную работу, которая при этом будет так или иначе совершена в run(). | ||
+ | ** также можно вычислить и теги для будущего вывода, хотя практического смысла это не имеет | ||
+ | * run() - запускается, только если результат не удалось загрузить из кэша. run() делает следующее: | ||
+ | ** вычисляет вывод (HTML) | ||
+ | ** вычисляет теги кэша для вывода (если только check() это уже не сделал) | ||
+ | ** запускает подзапросы (если только их, опять-таки, не запускал check()) | ||
+ | |||
+ | Частый кейс: вкрапления ссылок "править" или других мелких зависящих от пользователя элементов (например, цен товаров в валюте пользователя). Варианты решения: | ||
+ | * добавить ID пользователя в ключ кэша - нам не подходит, пользователей ведь будет много, только кэш забьётся лишним мусором | ||
+ | * вынести подстановку этих элементов на javascript, в браузер - вменяемое решение, но генерирует много дополнительных запросов и замедляет полную загрузку страницы | ||
+ | * наше решение: отдельный небольшой дочерний контроллер, в который через параметры передаются готовые данные для определения, выводить или не выводить ссылку (например, набор пользователей, которым разрешено править выведенный элемент). Эти параметры будут кэшированы вместе с основным выводом в наборе подзапросов, таким образом, после загрузки HTML из кэша можно будет проверить эти разрешения "малой кровью" и вывести (или не выводить) ссылки. | ||
+ | |||
+ | Порядок обработки запроса: | ||
+ | # Создать главный контроллер | ||
+ | # Создать контроллер layout'а (почти ничем не отличается от обычного, кроме того, что является как бы "подзапросом" главного, но при этом не подставляется в его вывод, а оборачивает его вывод) | ||
+ | # Начиная с главного контроллера: | ||
+ | ## Вызываем check() | ||
+ | ## Пробуем загрузить вывод из кэша | ||
+ | ## Если вывод загрузился и актуален, и если check() не вызывал subrequest() — загружаем записи о подзапросах из вывода, загруженного из кэша | ||
+ | ## Если не загрузился: | ||
+ | ### Если check() вычислил теги для будущего ключа кэша - НЕ сбрасываем их! | ||
+ | ### Вызываем run() | ||
+ | ### Сохраняем вывод, теги и (если подзапросы вызывались в run()) записи о подзапросах в кэш | ||
+ | ## Делаем (3) со всеми контроллерами подзапросов | ||
+ | ## Подставляем выводы подзапросов в вывод их родительских контроллеров | ||
+ | # Делаем (3) с контроллером layout'а | ||
+ | # Вычисляем Last-Modified из тегов кэша всех контроллеров (время модификации тега обновляется при каждом его сбросе) | ||
+ | # Сравниваем Last-Modified с запрошенным If-Modified-Since и отправляем HTTP 304, если кэш браузера актуален | ||
+ | # Отправляем вывод обычным образом | ||
== English == | == English == |
Версия 13:09, 18 мая 2016
Об одном правильном подходе к HMVC (Hierarchical MVC) и кэшированию HTML на стороне сервера.
Делим страницу на блоки, каждый блок обрабатывается одним контроллером. При этом контроллеры образуются в "иерархию" - каждый блок может вызывать другие и подставлять внутрь себя их вывод. Каждый вызов контроллера описывается именем контроллера и параметрами, в которых, аналогично URL-параметрам, допускаются только сериализованные данные, а не объекты.
Для корректной инвалидации кэша используем теги.
Далее запуск каждого контроллера делится на два метода:
- check() - запускается всегда, перед попыткой загрузить результат из кэша. Здесь должны выполняться следующие задачи:
- обработка запросов на модификацию данных (и редирект после сохранения изменений)
- валидация параметров запроса, редиректы на очищенные URL
- вычисление ключа кэша
- если хочется, вызов будущих подзапросов (метод subrequest()). это бывает полезно, если не хочется множить ключи кэша в зависимости от набора подзапросов. в то же время, это бывает вредно, если для вызова подзапросов нужно совершить дополнительную работу, которая при этом будет так или иначе совершена в run().
- также можно вычислить и теги для будущего вывода, хотя практического смысла это не имеет
- run() - запускается, только если результат не удалось загрузить из кэша. run() делает следующее:
- вычисляет вывод (HTML)
- вычисляет теги кэша для вывода (если только check() это уже не сделал)
- запускает подзапросы (если только их, опять-таки, не запускал check())
Частый кейс: вкрапления ссылок "править" или других мелких зависящих от пользователя элементов (например, цен товаров в валюте пользователя). Варианты решения:
- добавить ID пользователя в ключ кэша - нам не подходит, пользователей ведь будет много, только кэш забьётся лишним мусором
- вынести подстановку этих элементов на javascript, в браузер - вменяемое решение, но генерирует много дополнительных запросов и замедляет полную загрузку страницы
- наше решение: отдельный небольшой дочерний контроллер, в который через параметры передаются готовые данные для определения, выводить или не выводить ссылку (например, набор пользователей, которым разрешено править выведенный элемент). Эти параметры будут кэшированы вместе с основным выводом в наборе подзапросов, таким образом, после загрузки HTML из кэша можно будет проверить эти разрешения "малой кровью" и вывести (или не выводить) ссылки.
Порядок обработки запроса:
- Создать главный контроллер
- Создать контроллер layout'а (почти ничем не отличается от обычного, кроме того, что является как бы "подзапросом" главного, но при этом не подставляется в его вывод, а оборачивает его вывод)
- Начиная с главного контроллера:
- Вызываем check()
- Пробуем загрузить вывод из кэша
- Если вывод загрузился и актуален, и если check() не вызывал subrequest() — загружаем записи о подзапросах из вывода, загруженного из кэша
- Если не загрузился:
- Если check() вычислил теги для будущего ключа кэша - НЕ сбрасываем их!
- Вызываем run()
- Сохраняем вывод, теги и (если подзапросы вызывались в run()) записи о подзапросах в кэш
- Делаем (3) со всеми контроллерами подзапросов
- Подставляем выводы подзапросов в вывод их родительских контроллеров
- Делаем (3) с контроллером layout'а
- Вычисляем Last-Modified из тегов кэша всех контроллеров (время модификации тега обновляется при каждом его сбросе)
- Сравниваем Last-Modified с запрошенным If-Modified-Since и отправляем HTTP 304, если кэш браузера актуален
- Отправляем вывод обычным образом
English
Our own HMVC. Each controller has 2 methods:
- check() — always runs before trying to load the output from cache:
- handles modification requests (and redirects after applying changes),
- validates parameters, redirects to handsome URLs
- calculates cache key
- also it MAY calculate cache tags and do subrequests if it wants to
- run() — runs only on cache miss:
- calculates output HTML
- MAY calculate cache tags and do subrequests if check() does not want to :-)
Request workflow:
- Create main controller
- Create layout controller
- Starting with the main controller:
- Call check()
- Try to load output from cache
- If loaded and valid, and if check() did not call subrequest() — load subrequest records from cached output
- If not loaded:
- If check() calculated some cache tags — do NOT reset them
- Call run()
- Save output, tags and subrequest records to cache
- Do (3) with all subrequest controllers
- Replace placeholders in output with subrequest outputs
- Do (3) with the layout controller
- Calculate Last-Modified from cache tags of all controllers (each tag flush updates its modification time)
- Compare Last-Modified with the requested If-Modified-Since and send HTTP 304 if not modified
- Send generated output normally