Патчи ECOS =================== .. contents:: :depth: 2 **Патч ECOS** - это некоторая работа, которую нужно выполнить один раз после запуска системы. Патчи могут быть использованы для автоматической или ручной миграции. Патчи ECOS - это артефакты с типом **app/patch**, которые подчиняются тем же правилам деплоя, что и остальные артефакты. Уникальность патча в пределах системы определяется по **targetApp + id** Модель --------- .. code-block:: id: String // идентификатор патча name: MLText // имя патча targetApp: String // целевое приложение где патч будет выполнен. По умолчанию - текущее приложение. date: Instant // дата патча. Подробнее ниже. manual: Boolean // ручной запуск патча. Если флаг равен true, то патч не будет выполнен автоматически. dependsOn: String[] // зависимость от других патчей type: String // тип патча config: ObjectData // конфигурация патча Типы патчей ------------- .. list-table:: :widths: 3 5 10 :header-rows: 1 :class: tight-table * - Тип - Конфигурация - Описание * - **bean** - **beanId: String** - идентификатор бина для исполнения - | Запустить бин в контексте приложения. | Поддерживаются следующие интерфейсы: * Runnable * Function0<*> * Callable<*> * StatefulEcosPatch<*> * - **delete** - **records: RecordRef[]** - Удалить указанные записи * - **mutate** - | **records: RecordAtts[]** | **record: RecordAtts** где RecordAtts: * id: String * attributes: Map - | Произвести мутацию указанных записей; | Если в конфигурации указаны records, то используются они и поле **record** игнорируется; | Если в конфигурации не указаны records, то используется record. Дата патча ----------- **Дата патча** - это ISO8601 ZULU значение даты и времени (напр. 2022-01-01T00:00:00Z). Это время заполняется при создании патча вручную текущей датой. Точное соответствие времени не требуется, но желательно чтобы дата указывалась хотя бы с точностью до дней. Дата патча используется для нескольких вещей: 1. Для сортировки патчей. Патчи с меньшим временем будут исполняться раньше. Эта гарантия действует только для патчей в пределах одного приложения. Т.е. тогда когда мы описываем патчи в приложении, которые будут исполняться в рамках того же приложения. Для строгой зависимости нужно использовать поле dependsOn. 2. Для возможности управления исполнением патча. Решение о исполнении патча принимается на основе старого и нового значений даты: a. Если старая_дата отсутствует (новый патч), то патч исполнится; b. Если старая_дата >= новая_дата, то патч считается выполненным и не исполнится; c. Если старая_дата < новая_дата, то патч считается обновленным и ставится в очередь на исполнение; Таким образом, если мы хотим, чтобы уже сработавший патч перевыполнился при следующем обновлении, то мы просто увеличиваем время в date. Использование в коде ---------------------- Для описания патчей можно использовать аннотацию **@EcosPatch** Для указания зависимостей можно использовать аннотацию **@EcosPatchDependsOn** .. code-block:: @Component @EcosPatchDependsOn("other-patch") @EcosPatch("test-patch", "2022-01-01T00:00:00Z") class TestComponent : Callable { override fun call(): Any { println("Patch executed") // Результат будет записан в БД ecos-apps, чтобы его можно было потом посмотреть. // Из требований к результату - только возможность конвертации через Json.mapper.toString в json строку. return "custom-result" } } Stateful патчи -------------- Для выполнения некоторой объемной работы можно использовать Stateful патчи (патчи с состоянием). Их суть заключается в том, что при выполнении патча мы не делаем всю работу сразу, а выполняем некоторую часть работы, после чего возвращаем промежуточное состояние процесса. Для реализации Stateful патча нужно реализовать интерфейс **StatefulEcosPatch**, где **Config** - это произвольный тип, который представляет состояние выполнения. Важно, чтобы инстанс этого класса можно было создать из пустого объекта ({}) т.к. это начальное состояние для патча. Пример в коде: .. code-block:: @EcosPatch("stateful-patch", "2022-01-01T00:00:00Z") class TestWithState : StatefulEcosPatch { override fun execute(state: ObjectData): PatchExecutionState { val counter = state.get("counter", 0) + 1 val completed = counter == 5 log.info { "Execute stateful patch. Counter: $counter, Completed: $completed" } return PatchExecutionState( ObjectData.create() .set("counter", counter), completed ) } } Архитектура ------------- .. image:: _static/patches/patches_1.png :width: 600 :align: center Из приложения артефакты патчей попадают в **ecos-apps** по стандартному механизму деплоя артефактов и сохраняются в БД. Далее **ecos-apps** периодически опрашивает таблицу патчей на наличие тех, которые можно применить (т.е. **targetApp** доступен и статус патча позволяет его применить). Если патч для применения нашелся, то мы выполняем команду на выполнение патча и отправляем её в **targetApp**. Результат выполнения команды мы кладем в БД. Если при выполнении патча возникла ошибка, то мы сохраняем эту ошибку в БД и через некоторое время повторяем попытку применить патч.