Хорошая архитектура CMS

в 7:59, , рубрики: cms, cms разработка, Веб-разработка, метки: ,
Введение

На сегодняшний день существует огромное количество хороших и плохих, узкоспециализированных и всеохватывающих CMS/CMF. Лично я тесно общался с 1С-Битрикс и Drupal, немного знаю о WordPress, Joomla. Прекрасно понимаю, что этого вполне может быть недостаточно для того, чтобы разработать свою, действительно удобную, производительную, хорошую и т.п. (список можно продолжать бесконечно) платформу для создания сайтов. Но все-таки я очень хотел бы попробовать.

Все, что будет написано дальше, не претендует на истиность в последней инстанции, только мое мнение. Говоря «хорошая архитектура» я буду иметь в виду «на мой взгляд хорошей архитектурой могла бы быть...».

Тезисы

Итак, несколько тезисов, касающихся разработки хорошей архитектуры:

  1. Использование ОО-подхода.
  2. Полный контроль над загрузкой страницы, т.е. запросы обрабатываются не физическими файлами на сервере (типа /about/index.php), а системой. По сути все запросы отправляются в корень сайта, затем система определяет, что отдать в ответе.
  3. Достаточно очевидный момент, но все же я его выделю. Запрос обрабатывается исключительно исходя из следующих данных: URL запроса, параметров GET, параметров POST, переменных сессии (там же и cookie).
  4. Естественно, логика четко отделена от представления. Для вывода того или иного блока всегда должен быть некий шаблон по умолчанию, определяемый модулем, ядром и т.п., в зависимости от принадлежности. Любой из этих шаблонов может быть переопределен. То есть должна быть некая иерархия шаблонов; в простейшем случае это может быть, например, шаблон модуля по умолчанию и переопределенный шаблон. Естественно, если система находит переопределенный шаблон, она применяет его. Привет Друпалу.
  5. Никакого HTML в программном коде! Весь HTML-код должен быть только в шаблонах и никак иначе. Дадим возможность верстальщику, не сведущему в программировании, самостоятельно натягивать верстку на сайт.
  6. Вытекает из предыдущего. Люди, имеющие дело с сайтом, должны иметь минимум сложностей при выполнении своей работы. Верстальщику — набор переменных в файлах шаблона, контент-менеджеру — удобные фильтры по данным, редактирование данных прямо со страницы на сайте, системному администратору — отдельный раздел в админке, программисту — все и вся. Утопия, конечно, но это то, к чему бы стоило стремиться. Ну и, естественно, компромиссные решения должны быть оправданными.

Я определил набор тезисов-требований к системе. Теперь можно перейти непосредственно к проектированию.

Последовательность действий

Начну я с последовательности действий, происходящих при запросе к системе.

  1. Пользователь заходит на сайт
  2. Система получает URL, GET и POST параметры
  3. Инициализируется обработчик запроса
  4. Обработчик определяет, какие модули необходимы для удовлетворения запроса клиента, включает их
  5. Выполняется необходимый для загрузки код модулей
  6. Модули определяют, что нужно выполнить для удовлетворения запроса
  7. Запускаются компоненты вывода, определенные на предыдущей стадии модулями
  8. Все обработанные компонентами и модулями данные в удобочитаемом виде отдаются шаблонам
  9. Включаем готовый HTML на страницу
  10. Отдаем клиенту ответ
Сущности системы

Исходя из вышесказанного возникает необходимость в определении сущностей, необходимых системе.

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

Классы системы

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

  • System — класс системы, он же хранит обработанные данные запроса
  • Handler — класс обработчика, определяет, какие модули необходимо включить
  • Module — класс модуля, содержит необходимый модулю код; сам по себе является абстрактным (или вообще не классом, а интерфейсом), так как только конкретный модуль может иметь смысл. Каждый написанный модуль — наследник этого класса (или реализация интерфейса)
  • Component — класс компонента вывода, отвечает непосредственно за код, передаваемый шаблону. У каждого модуля может быть какое-то количество компонентов, привязанных к нему. Возможно, стоит включить возможность создания независимых компонентов, но это усложнит структуру и целесообразность такой возможности на данной стадии лично мне сложно определить. Класс абстрактный по соображениям, аналогичным с классом Module
  • Page — класс-компоновщик страниц. Ему отдается все, что будет на странице, он же выводит данные из шаблонов

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

Важно помнить, что модули могут быть зависимы, равно как и компоненты. Чтобы добиться максимальной гибкости системы, на момент выполнения кода компонентов уже должен быть выполнен код и сформированы данные всех модулей, чтобы компоненты не были ограничены в доступе к данным модулей, отличных от того, к которому эти компоненты относятся. Для этого нужно дать доступ к необходимым полям объекта обработчика в коде компонентов и модулей. Само собой, это может привести к ряду ошибок, связанных с зависимостью компонентов и модулей, но это легко решить механизмом проверки этих зависимостей и определения очереди загрузки модулей и компонентов.

Модули не всегда удобны, так как иногда просто нужен доступ к каким-то классам и функциям, являющимся вспомогательными. Например, функции для работы с БД. В таком случае можно определить такое понятие, как библиотека и выделить его в отдельный блок.

Наследование

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

  • Нам необходима проверка в параметре запроса языка, выбранного пользователем и нужно выбранный язык поместить в переменную сессии. В таком случае наследуемся от класса System, переопределяем метод обработки данных запроса, дописываем в нем код создания переменной сессии с языком.
  • Нам необходима статистика вызова того или иного модуля, но мы не можем править его код непосредственно. Тогда определяем новый класс, наследуем его от модуля, необходимого для статистики, дописываем в него, например, код записи в БД данных статистики.
  • В компоненте не хватает данных для вывода, но мы не можем править его код. Опять-таки, наследование решает эту проблемы.

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


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

Автор: mr_T

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js