Работа механизма DataInstall#

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

  • регистрации состояний и связей между ними;

  • создания записей по умолчанию;

  • добавления типов объектов;

  • заполнения справочников;

  • создания служебных данных, необходимых для корректной работы модуля.

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

  • через инструмент разработки с поддержкой генерации метаданных;

  • или через команду upgrade по SSH.

DataInstall — естественная часть жизненного цикла развертывания: когда метаданные обновлены, система сравнивает версии и решает, какие скрипты требуется выполнить.

Ключевые особенности#

  • Автоматическое выполнение при генерации базы данных — запуск происходит через генератор таблиц (в среде разработки или через команду upgrade), как часть жизненного цикла развертывания.

  • Управление версиями скриптов — система выполняет скрипт только при первом появлении или изменении версии, сравнивая её с записями в таблице Btk_DataInstallScript.

  • Поддержка зависимостей между скриптами — перед выполнением проверяется наличие всех зависимостей; отсутствие требуемого скрипта — ошибка.

  • Отложенное выполнение (delayed) — скрипты с флагом delayed не выполняются в момент генерации, а регистрируются для запуска фоновой задачей в нерабочее время.

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

Общая схема процесса#

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

  1. Загрузка информации о ранее выполненных скриптах
    Система загружает данные о всех скриптах, которые уже были запущены ранее, включая их текущие версии и статус (обычный или отложенный). Это позволяет избежать повторного выполнения одних и тех же скриптов.

  2. Сбор всех доступных скриптов из метаданных
    Из всех классов и пакетов системы собираются все объявленные DataInstall-скрипты. Каждый скрипт получает уникальное имя, чтобы система могла однозначно его идентифицировать.

  3. Определение скриптов, требующих выполнения
    Сравниваются скрипты из метаданных с историей выполнения. Запуск необходим только для новых скриптов или тех, чья версия была увеличена. Отложенные скрипты выделяются отдельно.

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

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

  6. Выполнение скриптов по очереди
    Скрипты запускаются в установленном порядке. Если скрипт обычный — он выполняется немедленно. Если отложенный — он регистрируется для фонового запуска. Ошибки одного скрипта не останавливают выполнение остальных.

  7. Фиксация результатов в базе данных
    После завершения обновления система фиксирует информацию о всех успешно выполненных скриптах.

Хранение истории выполнения скриптов#

Информация о выполненных DataInstall-скриптах хранится в таблице btk_datainstallscript.
Она используется системой для определения, какие скрипты уже были запущены, и требуется ли их повторное выполнение при обновлении.

Таблица содержит следующие ключевые поля:

  • Имя — системное имя скрипта;

  • Версия — текущая версия скрипта;

  • Дата прогона — дата и время последнего успешного выполнения скрипта;

  • Пользователь — учётная запись, под которой было установлено подключение к базе данных при запуске скрипта.

Примечание

Подключение происходит только на уровне БД, пользователь не входит в саму ERP-систему.

  • Если указано sys — скрипт запускался через SSH с правами системного пользователя.

  • Если указан IP-адрес — подключение было выполнено напрямую с локального компьютера.

Создание DataInstall-скриптов#

DataInstall полностью определяется в коде, а именно:

  • в odm.xml для классов;

  • в pkg.xml для пакетов.

Разработчик сам пишет скрипт прямо в XML-метаданных, где указывает:

  • имя скрипта;

  • его версию;

  • зависимости;

  • флаг отложенного выполнения (если требуется);

  • текст JEXL.

Разработчик вручную добавляет в odm.xml или pkg.xml секцию <dbData> и прописывает в ней скрипт:

<dbData>
  <script name="dataInstall" version="1">
      <install>SomeApi.dataInstall();</install>
  </script>
</dbData>

Скрипт может содержать:

  • прямой JEXL-код;

  • вызов API-классов (частый вариант).

После этого JEXL-вызов будет выполнен генератором данных при обновлении версии скрипта.

Использование#

Пример реализации dataInstall#

Ниже приведен минимальный пример «от и до»: начиная с объявления класса и заканчивая Scala-API, который вызывается DataInstall-скриптом.

1. Фрагмент Rpltst_ForDeleteTest.odm.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<class xmlns="http://www.global-system.ru/xsd/global3-class-1.0"
       name="Rpltst_ForDeleteTest"
       caption="Тест удаления"
       supertype="reference"
       cardEditor.representation="Card"
       listEditor.representation="List"
       viewOptions.openCardType="mdi">

    <attributes>
        <attr name="id" attribute-type="Long" caption="Идентификатор" order="-1" type="basic" isVisible="false"/>
        <attr name="idClass" attribute-type="Long" caption="idClass" order="-2" type="basic" isVisible="false"/>
        <attr name="sSystemName" attribute-type="Varchar" caption="Системное имя" order="10" isMnemoCode="true"/>
        <attr name="sCaption" attribute-type="Varchar" caption="Наименование" order="20" isHeadLine="true"/>
        <attr name="sDescription" attribute-type="Varchar" caption="Описание" order="30"/>
        <attr name="idState" attribute-type="Long" caption="Состояние" order="70"
              type="refObject" ref.class="Btk_ClassState"/>
    </attributes>

    <dbData>
        <script name="dataInstall" version="1">
            <install>Rpltst_ForDeleteTestApi.dataInstall();</install>
        </script>
    </dbData>
</class>

Здесь определяется один скрипт с именем dataInstall, который вызывает метод dataInstall() в соответствующем API-классе.

2. Код API, вызываемый скриптом Rpltst_ForDeleteTestApi.scala

class Rpltst_ForDeleteTestApi
  extends Rpltst_ForDeleteTestDpi[Rpltst_ForDeleteTestAro, Rpltst_ForDeleteTestApi, Rpltst_ForDeleteTestAta] {

  override protected def entityAta: Rpltst_ForDeleteTestAta = Rpltst_ForDeleteTestAta

  def dataInstall(): Unit = {

    session.commit()
    Btk_Pkg().setRWSharedUOWEditType()

    // регистрация состояния "Формируется"
    Btk_ClassStateApi().register(
      idpMasterClass = idClass,
      spSystemName = "Rpltst_ForDeleteTestForming",
      spCaption = "Формируется",
      bpStartState = 1.nn,
      npOrder = 100.nn)
    session.flush()

    // регистрация состояния "Сформирован"
    Btk_ClassStateApi().register(
      idpMasterClass = idClass,
      spSystemName = "Rpltst_ForDeleteTestFormed",
      spCaption = "Сформирован",
      bpStartState = 0.nn,
      npOrder = 200.nn)
    session.flush()
  }
}

object Rpltst_ForDeleteTestApi extends ApiFactory[Long, Rpltst_ForDeleteTestAro, Rpltst_ForDeleteTestApi]

class Rpltst_ForDeleteTestAro extends Rpltst_ForDeleteTestDro
class Rpltst_ForDeleteTestAta extends Rpltst_ForDeleteTestDta[Rpltst_ForDeleteTestAro, Rpltst_ForDeleteTestApi, Rpltst_ForDeleteTestAta]
object Rpltst_ForDeleteTestAta extends Rpltst_ForDeleteTestAta

Этот метод создает два состояния класса. Именно такие операции и составляют типичные DataInstall-скрипты.

Пример заполнения справочника в dataInstall#

Ниже приведен пример заполнения справочника предопределенными значениями через dataInstall.

def register(spCode: NString,
             spCaption: NString,
             spDescription: NString = None): ApiRop = {
  val rop = getByMnemoCode(spCode).getOrElse {
    val rop = insert()
    setsCode(rop, spCode)
    rop
  }
  setsCaption(rop, spCaption)
  setsDescription(rop, spDescription)
  rop
}

def dataInstall(): Unit = {
  register(spCode = "Simple", spCaption = "Пример")
}

В этом примере метод register выполняет регистрацию записи справочника:

  • ищет запись по коду;

  • если запись не найдена, создает новую;

  • заполняет код, наименование и описание;

  • возвращает объект записи.

Метод dataInstall в данном примере вызывает register и добавляет в справочник запись с кодом Simple и наименованием Пример.

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

Особенности повторного выполнения dataInstall#

В большинстве случаев dataInstall используется для начальной установки данных. Однако при изменении крупных dataInstall-скриптов следует учитывать особенности их повторного выполнения.

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

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

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

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

Отложенные DataInstall-скрипты#

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

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

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

Отложенное выполнение применяется, когда нужно:

  • выполнить массовое заполнение или перерасчёт данных;

  • обновить большой объём существующих записей;

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

  • сократить время простоя системы при установке релиза;

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

Отложенный скрипт объявляется в метаданных так же, как обычный DataInstall-скрипт, но дополнительно помечается флагом отложенного выполнения.

<dbData>
    <script name="dataInstallLargeUpdate" version="1" delayed="true">
        <install>SomeApi.dataInstallLargeUpdate();</install>
    </script>
</dbData>

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

Общая схема работы отложенного скрипта:

  1. Отложенный DataInstall-скрипт регистрируется в таблице Btk_DelayedScript во время генерации таблиц.
    Список отложенных скриптов можно открыть в интерфейсе:

    Настройка системы > Настройки и сервисы > Дополнительно > Отложенные скрипты инсталляции (datainstall)

  2. Задание DelayedGeneratorJob «Выполнение отложенных скриптов» выполняет скрипты, зарегистрированные в Btk_DelayedScript. Обработка очереди выполняется ежедневно в 22:00 мск. Чтобы скрипт попал в ближайший запуск задания, он должен быть зарегистрирован в Btk_DelayedScript до этого времени.

  3. Если скрипт завершается с ошибкой, стек ошибки записывается в поле Btk_DelayedScript.sError. Такой скрипт остаётся в Btk_DelayedScript и будет повторно запущен при следующей обработке очереди заданием DelayedGeneratorJob. Если скрипт остаётся в очереди до 22:00 мск., он будет повторно обработан при ближайшем запуске задания.

  4. Если скрипт выполняется успешно, он регистрируется как обычный выполненный install-скрипт или поднимает версию уже зарегистрированного install-скрипта:

    • для dbSchema — в Btk_SchemaObject;

    • для dbData — через Btk_DataInstallScriptApi.

  5. После успешной регистрации или повышения версии скрипт удаляется из Btk_DelayedScript.

После обработки очереди состояние отложенного скрипта определяется по записи в Btk_DelayedScript: скрипты с ошибкой остаются в очереди, а успешно выполненные скрипты удаляются из неё.

Выбор обычного или отложенного выполнения

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

Внимание

Обычный DataInstall-скрипт должен выполняться быстро. В обычный DataInstall настоятельно не рекомендуется добавлять скрипты, выполнение которых занимает больше 10 секунд.

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

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

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

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

  • состояние до выполнения скрипта;

  • состояние после его успешного выполнения.

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

Отложенные DataInstall-скрипты не следует использовать для данных, без которых система не может стартовать после обновления. Такие данные должны устанавливаться обычным DataInstall-скриптом в процессе обновления. Отложенное выполнение предназначено для операций, которые можно безопасно выполнить отдельно от установки релиза.

Ограничения#

  • Нельзя понижать версию скрипта — система выполнит его повторно только при увеличении версии.

  • Изменение имени скрипта (name) приводит к созданию нового скрипта. Система определяет уникальность по полному системному имени (sSystemName). Старый скрипт остаётся в истории, новый добавляется отдельно — это может вызвать дублирование данных.

  • Отсутствие или ошибка в зависимом скрипте блокирует выполнение зависящего от него. При этом общий процесс не останавливается.

  • Обычный (не отложенный) скрипт не может зависеть от отложенного — такая связь считается некорректной и фиксируется как ошибка.

  • Порядок выполнения скриптов определяется исключительно через зависимости. Прямое управление очередностью невозможно.

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

  • Отложенные скрипты не выполняются сразу — они регистрируются в системе и запускаются фоновой задачей в нерабочее время.