.. _patches: Патчи Citeck ================= .. contents:: :depth: 2 **Патч** — разовая операция, выполняемая после запуска системы. Используется для автоматической или ручной миграции данных и конфигурации. Патчи Citeck реализованы как артефакты типа **app/patch** и подчиняются общим правилам деплоя артефактов. Уникальность патча в рамках системы определяется комбинацией **targetApp + id**. Модель ------------ .. code-block:: text 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:: kotlin @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" } } .. warning:: Если предполагается, что патч выполняет объемную работу и может выполняться более 30 секунд, то нужно использовать **StatefulEcosPatch**. Stateful патчи ------------------- Для выполнения некоторой объемной работы можно использовать Stateful патчи (патчи с состоянием). Их суть заключается в том, что при выполнении патча мы не делаем всю работу сразу, а выполняем некоторую часть работы, после чего возвращаем промежуточное состояние процесса. Для реализации Stateful патча нужно реализовать интерфейс **StatefulEcosPatch**, где **Config** - произвольный тип, который представляет состояние выполнения. Важно, чтобы инстанс этого класса можно было создать из пустого объекта ({}) т.к. это начальное состояние для патча. Пример в коде: .. code-block:: kotlin @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: 500 :align: center Из приложения артефакты патчей попадают в **ecos-apps** по стандартному механизму деплоя артефактов и сохраняются в БД. Далее **ecos-apps** периодически опрашивает таблицу патчей на наличие тех, которые можно применить (т.е. **targetApp** доступен и статус патча позволяет его применить). Если патч для применения нашелся, то мы выполняем команду на выполнение патча и отправляем её в **targetApp**. Результат выполнения команды мы кладем в БД. Если при выполнении патча возникла ошибка, то мы сохраняем эту ошибку в БД и через некоторое время повторяем попытку применить патч.