Pull to refresh

Строим плагиноориентированную модульную систему

Reading time 4 min
Views 3.4K
Года три назад у меня роились идеи относительно того, как создать такое ядро системы, которое позволяло бы быстро и эффективно расширять его функционал с помощью подключаемых модулей, расширений. При этом нужно было минимизировать проблемы, которые возникают у плагинописателей при развитии продукта, проблемы совместимости множества плагинов.

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

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



Ядро системы


Ядро системы, которая имеет модульную структуру обычно выглядит примерно так
Ядро системы

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

Системе приходит запрос, который обрабатывает специальный метод или объект ядра (request manager), задача которого состоит в определении того, что нужно сделать системе по запросу пользователя.

К ядру подключаются модули. Модуль представляет собой некий набор функций/методов, часть из которых публичные, т.е. доступны пользователю для исполнения. Не стоит путать с публичными методами объектов. Функции модуля могут быть публичными методами, но это не обязательное условие функционирования системы. Например, модуль новостей может иметь несколько функций: показать список новостей, показать конкретную новость, создать новость, редактировать новость, удалить новость, опубликовать новость и т.д.

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

Обработчики


Если спроектировать систему только на основании первой схемы, нужного нам эффекта не добиться. Реализация нового модуля потребует проектировки и реализации с нуля. А если мы хотим доработать существующий, то мы должны пойти по пути патчей существующих модулей, или по пути полной копии модуля и последующих доработок. Но система на месте не стоит, и при развитии модуля будет серьезно страдать совместимость. Патчи могут банально перестать работать. Придется постоянно следить за обновлениями или наоборот, замораживать процесс обновления, что не очень хорошо, так как в новых версиях продукта могут исправлять серьезные уязвимости или критические баги.

Вот тут на помощь придут обработчики. Обработчик — некая функция, которой может передаваться управление в процессе выполнения кода.

А теперь давайте типизируем каждую нашу публичную функцию до следующего вида.
Обработчики
Это абстрактная схема, а не код самой функции.

Рассмотрим типичный процесс выполнения, когда обработчики не установлены. Пусть наша абстрактная функция имеет обработчик onBefore, который вызывается перед процессом выполнения рабочего тела функции. Обработчик onAfter будет выполняться после того, как тело нашей функции выполнит какую-то полезную работу.

Сюда же я добавил специальный блок под названием templateIt. Это вызов процесса темплейтирования данных, в результате которого мы получим html-код, или любой другой текстовый документ. Это не обязательный блок, его может и не быть.

Например, мы вызываем функцию отображения конкретной новости. Тело функции выберет нужные данные и отправит их темплейт-системе.

В реальности будет немного сложнее.
Процесс выполнения
Системе передается get или post запрос, который представляет собой набор инструкций и данных. Инструкциями будет указание выполнить модуль новостей, а данными — идентификатор новости. Менеджер запросов должен разделить данные и инструкции, а потом ядро запросит нужную функцию модуля и передаст туда исходные данные. Функция выполнит обработку запроса и отправит рабочие данные темплейт-системе, которая сделает для нас красивый html-код.

Что же дают нам обработчики?


Приведу простой пример. Система умеет выводить список пользователей. Вы создали модуль, который имеет другой список пользователей, которые являются VIP-посетителями вашей системы. Вам захотелось не просто выводить список пользователей, но и показывать, кто из них является еще и VIP-посетителем.

Значит, вы должны добавить onAfter-обработчик функции, которая формирует список пользователей. Ваша функция получит набор данных в виде списка пользователей. Нужно будет всего лишь расставить персонам из этого списка признак VIP. Темплейт придется скопировать из исходной функции со списком пользователей, и внести туда нужные изменения.

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

На бумаге выглядит красиво, но не всегда красиво реализуемо. При динамическом исполнении кода ваш новый модуль должен быть запущен до того, как ядро передаст управление модулю, который формирует список пользователей. А кто знает, что это нужно делать? Инициализировать все модули сразу? Не очень эффективное решение.

А давайте немного изменим процедуру.

image

Во всю процедуру выполнения добавляем некий chaining manager (менеджер по связям :) ), основная задача которого заключается в следующем. Если функция модуля собирается выступать в роли обработчика, она изначально декларируется в менеджере связей. За выполнение обработчиков публичной функции и передаче данных темплейт системе ответсвенным становится менеджер связей. Публичная функция вырождается просто в тело функции, т.е. внутри нее нет другого кода, кроме как работа с данными.

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

image

Если мы сцепляем несколько функций, то нам нужно знать, что делать дальше. Например, после выполнения a.a1 у нас возникнет некий набор данных data1 и набор инструкций chaining instructions, в которых будет указано, что эта функция готова превратить данные в темплейт. Но, у этой функции определен onAfter-обработчик, а значит нужно выполнить b.b1, которая добавит к data1 еще и data2, но, темплейт эта функция может не изменять.

В свою очередь, и у этой функции может быть свой onAfter-обработчик, который создат свои данные на основании данных предыдущих функций, и скажет, что нужно провести процедуру тепмлейтирования данных. И так далее, до бесконечности.

Это уже намного лучше и гибче. Надеюсь, мои мысли помогут кому-то в реализации своих гениальных идей.

Приятного программирования!

UPD. Работа над ошибками.
Tags:
Hubs:
+40
Comments 107
Comments Comments 107

Articles