Architecture • Modular design

Модульная расширяемая архитектура

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

Plugins Модели Many‑to‑many Many‑to‑one One‑to‑many

Основные положения

Интерфейс загрузки плагинов и моделей должен быть минимальным.

Плагин может:
  • Зарегистрировать свои модели.
  • Получить чужие модели.
  • Корректно завершить работу при запросе ядра.
  • Хост отвечает за:
  • Поиск и загрузку модулей (плагинов).
  • Порядок инициализации.
  • Жизненный цикл моделей и модулей (плагинов).
  • Вся функциональность, доступная другим модулям, публикуется через модели (динамические библиотеки).

    Модель — контейнер и интерфейс

    Модель — это одновременно контейнер и интерфейс взаимодействия.

    Контейнер
    Модель может хранить общие (shared) данные, не являясь провайдером функциональности какого‑либо плагина.
    Интерфейс
    Модель может быть провайдером функциональности и экспортировать методы одного или нескольких плагинов.
    Модель становится “шиной” для плагинов: кто‑то публикует в неё возможности, кто‑то их потребляет. Сам хост в этих деталях не участвует.

    Паттерн many‑to‑many

    Многие плагины могут получать функционал через одну модель от многих плагинов

    Пример: InternalNetModel и транспорт

    InternalNetModel <--> InternalNetHttpsPlugin
    InternalNetModel <--> InternalNetTlsPlugin
    InternalNetModel <--> InternalNetTcpPlugin
    InternalNetModel <--> InternalNetUdpPlugin
    Для плагинов бизнес‑логики доступен только унифицированный интерфейс межмодульного взаимодействия, а транспорт выбирается за счёт набора загруженных модулей. Таким образом, транспорт не вшит монолитно в приложение, в разных ситуациях может быть не просто отключен, а полностью убран без выпуска новой версии, без доработок.

    Пример: PayModel и платёжные системы

    PayModel <--> PayFreeKassaPlugin
    PayModel <--> PayYouMoneyPlugin
    PayModel <--> PayWataPlugin
    PayModel <--> Pay...Plugin
    Этот приём одинаково применим к транспорту, платёжным системам, хранилищам, провайдерам логов — везде, где хочется иметь единый интерфейс и наращивать реализации по мере необходимости.

    Паттерн many‑to‑one

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

    Пример: VirtualMachineManager

    VitualMachineManagerModel <--> VitualMachineManagerPlugin
    VitualMachineManagerModel <--> ExternalVitualMachineManagerPlugin
    Даже при схеме many‑to‑one код не превращается в монолит: реализацию можно заменить (локальная/удалённая, другая версия), не трогая потребителей. Главное — сохранить интерфейс модели.

    Паттерн one‑to‑many

    One‑to‑many — просто другая сторона предыдущих паттернов. Если модули спроектированы как независимые провайдеры через модели, можно брать готовый набор модулей и собирать вокруг него совершенно разные приложения.

    Другие статьи