Гарантии runtime
Возобновление, повторная доставка, таймеры и идемпотентность воркфлоу.
Платформа хранит состояние воркфлоу и доставляет продолжение боту. Логика процесса остаётся в вашем TypeScript-коде.
Возобновление
При каждом продолжении handler запускается с начала. Выполненные шаги
возвращают сохранённый результат. Поэтому побочные эффекты должны находиться
внутри step().
const result = await step('reserve-stock', async () => {
return inventory.reserve(input.orderNumber)
})Код между шагами может выполниться несколько раз. Держите его чистым и детерминированным.
Длительность воркфлоу и одного запуска
timeout ограничивает полную жизнь экземпляра воркфлоу: она может составлять
минуты, часы или дни. Один запуск handler — это короткий runtime invocation с
бюджетом до двух минут. Перед исчерпанием бюджета runtime сохраняет dirty state,
уступает выполнение платформе и получает новое продолжение.
Поэтому долгую работу делите на устойчивые checkpoint-ы:
for (const batch of batches) {
await step(`index-${batch.id}`, () => indexBatch(batch))
}После возобновления завершённые шаги реплеятся из сохранённых результатов, а
первый незавершённый шаг выполняется. Не увеличивайте HTTP timeout, чтобы
«уместить» весь процесс в один invocation: это ломает возобновление и не
увеличивает timeout самого воркфлоу.
Платформа выдаёт invocation execution lease, runtime продлевает её heartbeat-ом,
а eventId служит fencing-token. Пока lease активна, reconciler не запускает
второй обработчик. Если HTTP-ответ потерян после acknowledgement, доставка не
повторяется конкурентно; после штатного yield продолжение ставится сразу.
Доставка at-least-once
Runtime может повторить доставку после сетевой ошибки или перезапуска. Это означает, что обработчик должен быть идемпотентным.
At-least-once относится к доставке, которая не была подтверждена. Принятый
runtime-ом eventId отделяет потерянный HTTP-ответ от действительно не
доставленного события.
- Используйте стабильный workflow ID для одной бизнес-операции.
- Давайте каждому шагу стабильное имя.
- Передавайте idempotency key во внешнюю систему, если она его поддерживает.
- Не считайте HTTP timeout доказательством, что операция не выполнилась.
Повтор шага
step() повторяет ошибку с ограниченным backoff. maxAttempts задаёт верхнюю
границу попыток.
await step('sync-crm', syncCrm, { maxAttempts: 3 })После исчерпания попыток воркфлоу завершается как failed. Возвращать значение
по умолчанию вместо ошибки следует только тогда, когда это разрешено контрактом
процесса.
Таймеры
step.sleep() и step.sleepUntil() создают durable timer. Он хранится на
платформе и переживает остановку runtime.
await step.sleep('wait-before-retry', '15m')
await step.sleepUntil('send-reminder', reminderAt)timeout ограничивает жизнь всего экземпляра. После дедлайна процесс получает
терминальный статус timed_out.
Запросы и callback
step.request() переводит процесс в ожидание данных. Workflow.provide()
возобновляет его после проверки payload по схеме.
После завершения связанный диалог получает workflow_callback со статусом и
результатом. Обрабатывайте все терминальные статусы:
completed;failed;canceled;timed_out.
Наблюдаемость
Workflow ID и имена шагов должны помогать диагностике без раскрытия содержимого сообщений. Не помещайте персональные данные и токены в имена шагов, логи и метки.
Используйте мониторинг для связи callback, событий и логов. Повторную доставку не следует считать отдельной бизнес-операцией, если workflow ID совпадает.