Описание модульной инфраструктуры¶
В этой статье:
Техническая инфраструктура¶
Продукты Юниверс используют Java 9 Jigsaw + собственные расширения для межмодульного взаимодействия. Это решение имеет преимущества:
Jigsaw является частью платформы Java.
Jigsaw полностью предоставляет то, что требуется: модульность в виде jar-файлов без превнесения дополнительной сложности в виде новых API, которые нужно интегрировать и использовать, и дополнительной инфраструктуры, в которую нужно встраиваться.
Собственные расширения не привносят лишних зависимостей, легковесны и просты: в противовес системы для менеджмента межмодульного взаимодействия OSGi, имеющей сложную и громоздкую структуру.
Запуск кода модулей in-process избегает проблемы "константной добавленной сложности", присущей микросервисной архитектуре. Это очень сильно помогает в борьбе за производительность.
Управление модулями¶
Для управления модульной подсистемой в системе используется ModuleService, являющийся регистратором модулей, источником информации о модулях и средством управления модульной подсистемой.
Рисунок 1 - Схема ModuleService
В задачи ModuleService входит загрузка и запуск модулей во время старта системы.
Во время старта автоматически запускается только корневой контекст Spring и сканируется только
com.unidata.mdm.core
.После того, как core полностью считан и инициализирован, ModuleService сканирует архивы в поисках классов - дескрипторов, имплементирующих
org.unidata.mdm.common.types.Module
и указанных в дескрипторе jar-файлов в META-INF/MANIFEST.MF jar-файла (в стиле OSGi, см. ниже).Для всех найденых модулей выстраивается дерево зависимостей, сканируются пакеты и производится инициализация дочерних Spring контекстов в порядке взаимных зависимостей.
ModuleService сохраняет статус инсталляции каждого модуля в базе данных. В таблице состояния хранится:
Версия инсталлированного модуля (MANIFEST.MF или дескриптор)
Строковый ID модуля (MANIFEST.MF или дескриптор)
Тэг модуля (часть идентификатора, указывающая на возможную кастомизацию для заказчика, MANIFEST.MF или дескриптор)
Дата инсталляции
Результат инсталляции (успех/неудача)
Путь к файлу, из которого запускалась инсталляция
При старте системы модули, которые были переданы для инсталляции и потерпели в ходе инсталляции неудачу, более не стартуют. На UI статус таких модулей отмечен соответственно.
Сервис также выполняет задачи инсталляции модулей в кластере — загрузки, верификации и распространения инсталлируемого архива между узлами кластера.
Взаимодействие модулей¶
Между модулями прописываются явные зависимости. Удаление модуля, от которого зависят другие модули, вызывает также удаление зависимых модулей. Наличие неудовлетворенных зависимостей вызывает отказ в инсталляции модуля в систему.
Предполагается поддержка стандартных аннотаций Spring типа @Autowire или JSR330 @Inject, так как они являются необходимым и привычным элементом каждодневной разработки. Речь о том, чтобы модифицировать спринговый ContextFactory таким образом, чтобы сервисы модулей могли инжектится в экземпляры классов других модулей.
Альтернативно, в случае неудачи с подходом, описанным выше, можно использовать динамическое получение экземпляров сервисов из других модулей, основанное на boolean<M extends Module> ModuleService.isActive(Class<M> module)
и <T> T Module.getService(Class<T> serviceType)
, обработкой аннотаций @Module, либо схожими методами по id модуля (строка). Предполагается, что если модуль активен, то все его сервисы тоже активны.
Модули взаимодействуют между собой и с клиентским кодом только через публичные - или, для внутренних нужд системы, - полупубличные (*Ext) интерфейсы.
Описание модуля¶
Модулем является jar-файл, который можно инсталлировать в систему, и сведения о котором будут видны на UI в разделе «Управление модулями». Отличительной чертой модуля является наличие в архиве модуля реализации интерфейса org.unidata.mdm.common.types.Module
, указанного в дескрипторе в META-INF/MANIFEST.MF jar-файла модуля.
Модулем может быть исполняемый код, набор ресурсов, патч модели данных, набор REST- или SOAP-эндпоинтов, или любая другая функциональность. Также это могут быть полностью кастомные модули, написанные для конкретного заказчика.
В исходном коде модуль оформлен отдельным gradle (IDEA / Eclipse) подпроектом, который собирается в отдельный jar -файл. Какой конкретно модуль попадет в сборку для определенного заказчика зависит от конфигурации билда.
Модуль выполняет задачи инсталляции, миграции и обновления окружения в методе install(), который вызывается ModuleService во время процесса инсталляции / обновления.
Модуль выполняет задачи по удалению информации о себе в системе через метод uninstall().
Метод start() выполняется во время старта модуля и может быть нужен для нестандартных инициализаций.
Метод stop() вызывается во время остановки модуля по каким-либо причинам и служит для нестандартного освобождения ресурсов.
Правила для модулей¶
Для всех модулей действуют несколько правил:
Модуль имеет строковый id, совпадающий с именем корневого пакета модуля (например,
org.unidata.mdm.data
). По этому id он доступен из ModuleService.Модуль должен уметь вернуть сведения о себе — свою версию, состоящую из
major.minor.revision
, локализованное имя, локализованное описание, тэг, потенциально указывающий на специфичность сборки, точки интеграции, которые он поддерживает (Pipelines).Если модуль использует базу данных, он ведет свои данные в полностью изолированной схеме (например,
org_unidata_mdm_data
илиorg_unidata_mdm_core
), имя которой совпадает с id модуля, где точки заменены знаком подчеркивания.Если модуль пользуется базой данных, он держит свой пул соединений, который, однако, доступен для конфигурации из backend.properties в стиле org.unidata.mdm.data.datasource.minPoolSize = 10.
Модуль заполняет MANIFEST.MF в схожем со спецификацией OSGi стиле. Поддерживаются следующие атрибуты метаданных в META-INF/MANIFEST.MF:
Manifest-Version: 1.0
Unidata-Module-Class: org.unidata.mdm.data.module.DataModule
Модуль ведет свои миграции схемы данных самостоятельно. Инсталляция / обновление схемы производится при инсталляции / обновлении модуля самим модулем (предлагается перейти на database-migrator).
Модуль самостоятельно ведет свои i18n ресурсы.
Модуль определяет свои классы исключений (потребуется рефакторинг Unidata Exception API).
Модуль публикует поддерживаемые особенности (ModuleFeature), активность или неактивность которых регулируется действующей лицензией. Для этого модуль при старте взаимодействует с LicenseService, который считает из лицензии нужную информацию.
Модуль публикует поддерживаемые точки интеграции (Segment(s) в Pipelines).
Модуль хранит свои приватные настройки в module.properties, которые недоступны в других контекстах для других модулей и недоступны для редактирования во время выполнения.
Все расширения PostgreSQL устанавливаются в схему public, которая автоматически добавляется в search_path конфигурации всех датасорсов всех модулей.
Оформление модуля¶
Все модули должны придерживаться единых стандартов в структурировании кода.
Имена проектов модулей начинаются с имени корневого пакета модуля
org.unidata.mdm.*
илиcom.unidata.mdm.*
(org.unidata.mdm.core, org.unidata.mdm.data, org.unidata.mdm.dq, org.unidata.mdm.data.rest, org.unidata.mdm.data.soap, etc.).Все проекты размещают код в пакетах с общим префиксом —
com.unidata.mdm
для коммерческих модулей иorg.unidata.mdm
для модулей с открытым исходным кодом.Следующим элементом пути должен быть подпакет детализации (core, model, dq, search, data, matching, job, classifier etc.), указывающий на назначение модуля. Этот сегмент имени пакета является корневым пакетом модуля и образует id модуля.
Приватные классы реализаций сервисов, DAO и подобного кода должны находится в подпакетах impl следующего уровня —
com.unidata.mdm.dq.service.impl
,com.unidata.mdm.dq.dao.impl
, etc.Следующей частью пути должны быть подпакеты категорий классов. См. таблицу ниже:
Таблица 1 - Описание подпакетов категорий классов
type |
Представление данных в среднем слое, промежуточные и служебные типы, не предназначенные для транспортировки наверх или вниз. Ниже могут следовать подпакеты детализации (org.unidata.mdm.core.type.calculables, org.unidata.mdm.core.type.module, org.unidata.mdm.core.type.data, etc.). Иными словами, это место доменной модели. |
dao |
Если модуль взаимодействует с БД. |
po |
Если модуль взаимодействует с БД. Ниже могут следовать подпакеты детализации (org.unidata.mdm.core.po.security, org.unidata.mdm.core.po.module, etc.) |
service |
Интерфейсы публичных сервисов. Имплементации находятся в подпакете impl и имеют соответствующий суффикс. |
dto result |
Трансфер — объекты (возвращаемые типы). Ниже могут следовать подпакеты детализации (org.unidata.mdm.core.dto.security, org.unidata.mdm.core.dto.module, etc.). Классы должны заканчиваться на *Result и иметь общий префикс модуля. |
util |
Некатегоризированные статические утилиты |
сonvert |
Утилиты конвертации представления данных |
module |
Типы, относящиеся к метаданным самого модуля |
exception |
ID исключений модуля + локальные типы |
context |
Контексты (параметры вызовов сервисов). Классы должны заканчиваться на *Context и иметь общий префикс модуля. |
configuration |
Все, что относится к конфигурации модуля, в том числе, классы аннотированные @Configuration |
migration |
Миграции схемы данных |
serialization |
Для объектов, используемых при сериализации |
Цель такой организации — сосредоточить все детали имплементации модуля внутри самого модуля, сделав его герметичным, и определить общий шаблон, позволяя быстрое вхождение каждого участника команды в любую область системы, в том числе, в ранее неизвестную.
Модули имеют несколько характеристик:
Модули бывают обязательными и опциональными. Обязательные модули всегда присутствуют в системе в той или иной реализации. Опциональные могут отсутствовать. Их работоспособность регулируется лицензией продукта.
Модули бывают стандартными или нестандартными (редакция). Это также регулируется метаданными лицензии.
На кастомизацию модуля может указывать тэг, когда он имеет значение, отличное от «standard».
Модули могут хранить приватные метаданные в пространстве данных лицензии.