Перейти к содержанию

История событий и детерминированное воспроизведение

У процесса уже есть ожидание, сообщения, внешние операции и cleanup. Чтобы сопровождать его, нужно понимать записанную историю. Рассмотрим, чем Event History отличается от обычного журнала Node и как replay сопоставляет код с событиями. Результат урока — объяснить восстановление без мифа о сохранённом стеке и определить, какие изменения требуют проверки совместимости.

Для предметного примера берём стадию 09-compensation. В проекте также подготовлен replay.ts для будущей проверки истории финальным совместимым вариантом. Автор не запускал Workflow, поэтому реальная JSON history в архив не включена. Replay-программа здесь не выполнялась и не является подтверждением успешной проверки.

История хранит значимые события

История включает начало исполнения, планирование Activities, их окончательные результаты, таймеры и входящие сообщения. Это данные, по которым Workflow может восстановить решения. Обычная строка console.log Worker не заменяет соответствующее событие Service и не является источником истины для движения процесса.

Для одобренной заявки «Согласования» предметная последовательность выглядит так:

Заявка начата
reserveSlot запланирована и подтверждена
Signal editorDecision получен
условие решения выполнено
publish запланирована и подтверждена
Workflow завершён с published

Это схема, а не буквальный дамп Event History. Реальная история содержит дополнительные Workflow Tasks, атрибуты и идентификаторы. Такой формат объясняет зависимости: publish появилась после решения и брони. Он не обещает, что каждая промежуточная строка нашего исходника превращается в отдельное событие.

Не все retry-попытки Activity представлены одинаковым полным набором событий. Service оптимизирует хранение активного исполнения, а подробности попыток видны через состояние и журналы. Поэтому нельзя вычислять «сколько HTTP POST было» простым подсчётом одноимённых записей из произвольного экспорта. Для внешних эффектов также смотрят квитанции gateway.

Event History принадлежит конкретному Run ID. Если заявка была reset или continue-as-new, её жизненный цикл может включать несколько исполнений. При расследовании нужно сохранить выбранный Run ID, а не только читаемый Workflow ID. Иначе два участника могут обсуждать разные истории одной предметной заявки.

Как код восстанавливает переменные

При replay Workflow снова проходит код с тем же входом и записанными событиями. Завершённый reserveSlot получает прежний результат. Signal снова влияет на decision согласно истории, после чего условие выбирает ту же ветку. Так восстанавливаются переменные, которые обычный Node-процесс потерял при остановке.

Внешний HTTP-запрос завершённой Activity не исполняется заново как часть обычного replay. Это важная граница: Workflow-функция обращается к proxy, а результат связан с историей. Если разработчик вызовет fetch напрямую внутри Workflow, он выйдет за модель и создаст зависимость от текущего состояния сети.

Детерминированность означает совместимость решений и команд с записанной историей. Это не требование, чтобы код использовал только одну синтаксическую форму. Можно переименовать локальную переменную или вынести чистое вычисление, если последовательность значимых действий остаётся совместимой. Зато вставка новой Activity перед уже выполненной меняет команды.

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

Диагностика несовместимости

Представим старую историю, где первая Activity — reserveSlot. Новый код без защитной ветки сначала вызывает validateSubmission. При replay он предлагает другую команду, чем та, на которую опиралась история. Такая ошибка относится к совместимости Workflow, а не к отсутствию gateway или обычному конфликту HTTP.

Процесс может оставаться открытым, потому что Workflow Task не продвигается. Бесконечный перезапуск Worker с тем же несовместимым кодом не исправит причину. Нужен совместимый bundle либо корректно подготовленная стратегия изменения. Поэтому при релизе длительных процессов недостаточно убедиться, что новая заявка проходит с нуля.

История помогает сопоставить последний подтверждённый шаг и ожидаемую команду. Это особенно полезно после компенсации: видны решение редактора, ошибка Activity и попытка release. Внешний JSON gateway показывает фактические publication и reservations. Два источника отвечают на разные вопросы и должны быть согласованы по идентичности заявки.

Основная модель Event History описана в официальной документации. Конкретные имена и атрибуты событий читаются по фактическому экспорту, а не по сокращённой схеме учебника. Не следует вручную редактировать историю, чтобы «подогнать» её под код: это уничтожает смысл проверки.

Будущая replay-проверка

После самостоятельного исполнения читатель сохраняет JSON history через локальную Temporal UI. В исходниках replay.ts читает такой файл и вызывает:

await Worker.runReplayHistory({
  workflowsPath: require.resolve('./workflows/11-versioned'),
}, history);

Полный файл определяет history через JSON.parse и обработку ошибки. Финальная стадия используется потому, что следующий урок добавит совместимую patched-ветку поверх 09. Этот инструмент предназначен для проверки предложенного изменения, а не для публикации новых внешних эффектов.

Будущая команда npm run replay -- ./history.json требует настоящего экспорта. Пустой файл или придуманный набор событий не заменит проверку действующей истории. В комплекте нет history.json: автор не запускал приложение. Таким образом ссылка на программу не превращается в ложное утверждение о выполненном replay.

API Worker.runReplayHistory принимает историю и сообщает несовместимость через ошибку; описание находится в справке Worker. Успешный replay подтверждает совместимость с конкретным набором событий. Он не доказывает, что gateway доступен или что все возможные новые пользовательские ветки корректны.

Для значимого изменения нужны истории с предметно разными путями: одобрение, отказ, истечение срока и отмена. Здесь это план будущей проверки, не выполненный набор тестов. Выбор историй связан с риском изменения: если меняется cleanup, одна история успешной публикации не показывает совместимость отмены.

Объём данных и конфиденциальность

Аргументы заявки, Signals и Activity results входят в систему истории и могут содержать пользовательские данные. Поэтому title и editor в реальном проекте требуют политики доступа и хранения. Секреты не передают туда ради удобства Activity: Worker может получить credential из своего защищённого окружения.

История имеет ограничения размера и количества событий. Бесконечный Workflow с каждым ежедневным сообщением увеличивает накопленное состояние. «Согласование» конечное и короткое по числу действий, поэтому искусственно делить его на новые runs не требуется. Для постоянно живущей сущности продолжение истории проектируют отдельно.

После урока восстановление становится объяснимым: события сохраняются Service, код воспроизводит решения, внешние результаты возвращаются из истории. Следующий материал рассматривает patched и совместимое изменение процесса, чтобы длительные заявки могли пережить выпуск нового Worker.

Оглавление серии