HMVC

Материал из YourcmcWiki
Версия от 16:13, 21 мая 2016; VitaliyFilippov (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Об одном правильном подходе к HMVC (Hierarchical MVC) и кэшированию HTML на стороне сервера.

Делим страницу на блоки, каждый блок обрабатывается одним контроллером. При этом контроллеры образуются в «иерархию» — каждый блок может вызывать другие и подставлять внутрь себя их вывод. Каждый вызов контроллера описывается именем контроллера и параметрами, в которых, аналогично URL-параметрам, допускаются только сериализованные данные, а не объекты.

Для корректной инвалидации кэша используем теги. Для реализации тегов в кэше, который сам по себе их не поддерживает, вместе с каждым ключом кэша будем сохранять «номера версий» всех связанных с ним тегов, а номер версии самого тега будем хранить в отдельной легковесной БД типа redis (либо в самом кэше, если он позволяет пометить ключ как невытесняемый). При чтении ключа из кэша будем сверять номера версий всех его тегов с новыми номерами версий тегов, и если в прочтённом ключе фигурирует более старая версия тега — будем вести себя так, как будто прочтённый ключ в кэше отсутствует. Таким образом, сброс ключей по тегу превращается в простой инкремент его номера версии. Также в ключе «информации о теге», кроме номера версии, мы будем сохранять время его модификации, чтобы потом легко вычислять время последней модификации страницы на основании одних только тегов кэша.

Далее запуск каждого контроллера делится на два метода:

  • check() — запускается всегда, перед попыткой загрузить результат из кэша. Здесь должны выполняться следующие задачи:
    • обработка запросов на модификацию данных (и редирект после сохранения изменений), либо отключение кэширования для таких запросов, чтобы они дошли до run()
    • валидация параметров запроса, редиректы на очищенные URL
    • вычисление ключа кэша
    • если хочется, вызов будущих подзапросов (метод subrequest()). это бывает полезно, если не хочется множить ключи кэша в зависимости от набора подзапросов. в то же время, это бывает вредно, если для вызова подзапросов нужно совершить дополнительную работу, которая при этом будет так или иначе совершена в run().
    • также можно вычислить и теги для будущего вывода, хотя практического смысла это не имеет
  • run() — запускается, только если результат не удалось загрузить из кэша. run() делает следующее:
    • вычисляет вывод (HTML)
    • вычисляет теги кэша для вывода (если только check() это уже не сделал)
    • запускает подзапросы (если только их, опять-таки, не запускал check())

Частый кейс: вкрапления ссылок «править» или других мелких зависящих от пользователя элементов (например, цен товаров в валюте пользователя). Варианты решения:

  • добавить ID пользователя в ключ кэша — нам не подходит, пользователей ведь будет много, только кэш забьётся лишним мусором
  • вынести подстановку этих элементов на javascript, в браузер — вменяемое решение, но генерирует много дополнительных запросов и замедляет полную загрузку страницы
  • наше решение: отдельный небольшой дочерний контроллер, в который через параметры передаются готовые данные для определения, выводить или не выводить ссылку (например, набор пользователей, которым разрешено править выведенный элемент). Эти параметры будут кэшированы вместе с основным выводом в наборе подзапросов, таким образом, после загрузки HTML из кэша можно будет проверить эти разрешения «малой кровью» и вывести (или не выводить) ссылки.

Порядок обработки запроса:

  1. Создать главный контроллер
  2. Создать контроллер layout’а (почти ничем не отличается от обычного, кроме того, что является как бы «подзапросом» главного, но при этом не подставляется в его вывод, а оборачивает его вывод)
  3. Начиная с главного контроллера:
    1. Вызываем check()
    2. Пробуем загрузить вывод из кэша
    3. Если вывод загрузился и актуален, и если check() не вызывал subrequest() — загружаем записи о подзапросах из вывода, загруженного из кэша
    4. Если не загрузился:
      1. Если check() вычислил теги для будущего ключа кэша — НЕ сбрасываем их!
      2. Вызываем run()
      3. Сохраняем вывод, теги и (если подзапросы вызывались в run()) записи о подзапросах в кэш
    5. Делаем (3) со всеми контроллерами подзапросов
    6. Подставляем выводы подзапросов в вывод их родительских контроллеров
  4. Делаем (3) с контроллером layout’а
  5. Вычисляем Last-Modified из тегов кэша всех контроллеров (помним, что время модификации тега обновляется при каждом его сбросе!)
  6. Сравниваем Last-Modified с запрошенным If-Modified-Since и отправляем HTTP 304, если кэш браузера актуален
  7. Отправляем вывод обычным образом

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:

  1. Create main controller
  2. Create layout controller
  3. Starting with the main controller:
    1. Call check()
    2. Try to load output from cache
    3. If loaded and valid, and if check() did not call subrequest() — load subrequest records from cached output
    4. If not loaded:
      1. If check() calculated some cache tags — do NOT reset them
      2. Call run()
      3. Save output, tags and subrequest records to cache
    5. Do (3) with all subrequest controllers
    6. Replace placeholders in output with subrequest outputs
  4. Do (3) with the layout controller
  5. Calculate Last-Modified from cache tags of all controllers (each tag flush updates its modification time)
  6. Compare Last-Modified with the requested If-Modified-Since and send HTTP 304 if not modified
  7. Send generated output normally