Патчи ECOS

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

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

Патчи ECOS - это артефакты с типом app/patch, которые подчиняются тем же правилам деплоя, что и остальные артефакты.

Уникальность патча в пределах системы определяется по targetApp + id

Модель

id: String // идентификатор патча
name: MLText // имя патча
targetApp: String // целевое приложение где патч будет выполнен. По умолчанию - текущее приложение.
date: Instant // дата патча. Подробнее ниже.
manual: Boolean // ручной запуск патча. Если флаг равен true, то патч не будет выполнен автоматически.
dependsOn: String[] // зависимость от других патчей
type: String // тип патча
config: ObjectData // конфигурация патча

Типы патчей

Тип

Конфигурация

Описание

bean

beanId: String - идентификатор бина для исполнения

Запустить бин в контексте приложения.
Поддерживаются следующие интерфейсы:
  • Runnable

  • Function0<*>

  • Callable<*>

  • StatefulEcosPatch<*>

delete

records: RecordRef[]

Удалить указанные записи

mutate

records: RecordAtts[]
record: RecordAtts

где RecordAtts:

  • id: String

  • attributes: Map<String, Any?>

Произвести мутацию указанных записей;
Если в конфигурации указаны records, то используются они и поле record игнорируется;
Если в конфигурации не указаны records, то используется record.

Дата патча

Дата патча - это ISO8601 ZULU значение даты и времени (напр. 2022-01-01T00:00:00Z). Это время заполняется при создании патча вручную текущей датой. Точное соответствие времени не требуется, но желательно чтобы дата указывалась хотя бы с точностью до дней. Дата патча используется для нескольких вещей:

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

  2. Для возможности управления исполнением патча. Решение о исполнении патча принимается на основе старого и нового значений даты:

    1. Если старая_дата отсутствует (новый патч), то патч исполнится;

    2. Если старая_дата >= новая_дата, то патч считается выполненным и не исполнится;

    3. Если старая_дата < новая_дата, то патч считается обновленным и ставится в очередь на исполнение;

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

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

Для описания патчей можно использовать аннотацию @EcosPatch

Для указания зависимостей можно использовать аннотацию @EcosPatchDependsOn

@Component
@EcosPatchDependsOn("other-patch")
@EcosPatch("test-patch", "2022-01-01T00:00:00Z")
class TestComponent : Callable<Any> {

    override fun call(): Any {
        println("Patch executed")
        // Результат будет записан в БД ecos-apps, чтобы его можно было потом посмотреть.
        // Из требований к результату - только возможность конвертации через Json.mapper.toString в json строку.
        return "custom-result"
    }
}

Stateful патчи

Для выполнения некоторой объемной работы можно использовать Stateful патчи (патчи с состоянием).

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

Для реализации Stateful патча нужно реализовать интерфейс StatefulEcosPatch<Config>,

где Config - это произвольный тип, который представляет состояние выполнения.

Важно, чтобы инстанс этого класса можно было создать из пустого объекта ({}) т.к. это начальное состояние для патча.

Пример в коде:

@EcosPatch("stateful-patch", "2022-01-01T00:00:00Z")
class TestWithState : StatefulEcosPatch<ObjectData> {

    override fun execute(state: ObjectData): PatchExecutionState<ObjectData> {
        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
        )
    }
}

Архитектура

../_images/patches_1.png

Из приложения артефакты патчей попадают в ecos-apps по стандартному механизму деплоя артефактов и сохраняются в БД.

Далее ecos-apps периодически опрашивает таблицу патчей на наличие тех, которые можно применить (т.е. targetApp доступен и статус патча позволяет его применить).

Если патч для применения нашелся, то мы выполняем команду на выполнение патча и отправляем её в targetApp. Результат выполнения команды мы кладем в БД.

Если при выполнении патча возникла ошибка, то мы сохраняем эту ошибку в БД и через некоторое время повторяем попытку применить патч.