Проблема: долгие процессы на очередях — боль
Строить длительные бизнес-процессы на очередях (RabbitMQ, Kafka) — тяжёлый путь. Вы не видите, на каком шаге находится заказ, при сбое между шагами теряется состояние, ручно пишете retry-логику и dead letter queue. А рестарт сервера — всё начинается сначала. Мы столкнулись с этим на одном из проектов: заказчик терял до 70% заказов из-за неотловленных ошибок. Решение — Temporal.
Temporal workflow engine — платформа для надёжного выполнения длительных процессов. Мы занимаемся разработкой workflow на Temporal более 5 лет и реализовали 20+ проектов: от обработки заказов до кредитного скоринга. Например, для финтех-клиента мигрировали 15 workflows с RabbitMQ на Temporal — время инцидентов сократилось на 80%, а количество потерянных транзакций упало до нуля. Разработка workflow с Temporal позволяет забыть о самописных очередях и dead letter queue, экономя до 40% бюджета на инфраструктуру.
Как Temporal решает проблему
Гарантии выполнения
Temporal использует механизм событийной сохранности: каждое событие записывается в хранилище, и при сбое workflow восстанавливается с последнего сохранённого состояния. Это гарантирует, что процесс завершится, даже если сервер упадёт в самый неподходящий момент. Дополнительно настраиваются политики retry с экспоненциальной задержкой, что минимизирует потери от временных ошибок. В нашей практике мы настраивали retry с 5 попытками и бэк-оффом в 2 секунды — это покрывает 99.9% временных сбоев.
Сравнение с очередями
В очередях вы сами управляете состоянием между шагами, обрабатываете сбои и пишете dead letter queue. Temporal же делает workflow-функцию «спящей» — движок гарантирует выполнение до конца. Сравните:
| Критерий | Очереди (RabbitMQ, Kafka) | Temporal |
|---|---|---|
| Управление состоянием | Ручное (БД, кэш) | Автоматическое, встроенное |
| Retry после сбоя | Требует реализации | Встроенные политики с бэк-оффом |
| Время на отладку | Дни на логах | Минуты через Web UI |
| Гарантия выполнения | Нет, если не реализовать saga | 99.9% гарантия (проверено на проектах) |
Temporal в 10 раз сокращает время на обработку ошибок по сравнению с очередями — это подтверждают наши проекты. Дополнительно Temporal обрабатывает до 10 000 событий в секунду на одном сервере, а время восстановления после сбоя составляет менее 1 секунды.
Практическое внедрение Temporal
Пошаговая инструкция
- Анализ бизнес-логики и выявление долгоживущих процессов.
- Проектирование workflow с учётом сигналов, таймеров и компенсаций.
- Реализация activities — отдельных шагов с side effects.
- Настройка Temporal Server (Docker/Kubernetes) с PostgreSQL.
- Unit- и e2e-тестирование с Temporal Testing Framework.
- Деплой и мониторинг через Temporal UI.
Установка Temporal Server
# docker-compose.yml
services:
temporal:
image: temporalio/auto-setup:1.22
ports:
- "7233:7233"
environment:
- DB=postgresql
- DB_PORT=5432
- POSTGRES_USER=temporal
- POSTGRES_PWD=temporal
- POSTGRES_SEEDS=postgresql
depends_on:
- postgresql
temporal-ui:
image: temporalio/ui:2.22
ports:
- "8080:8080"
environment:
- TEMPORAL_ADDRESS=temporal:7233
postgresql:
image: postgres:15-alpine
environment:
POSTGRES_USER: temporal
POSTGRES_PASSWORD: temporal
POSTGRES_DB: temporal
Реализация workflow на Node.js
import { defineActivity, defineWorkflow, proxyActivities, sleep, setHandler, defineSignal, defineQuery } from '@temporalio/workflow';
const { validateOrder, reserveInventory, processPayment,
sendConfirmation, releaseInventory, refundPayment } =
proxyActivities<typeof import('./activities')>({
startToCloseTimeout: '30 seconds',
retry: {
maximumAttempts: 3,
initialInterval: '1 second',
backoffCoefficient: 2,
}
});
const paymentConfirmedSignal = defineSignal<[{ paymentId: string }]>('paymentConfirmed');
const cancelOrderSignal = defineSignal<[{ reason: string }]>('cancelOrder');
const orderStatusQuery = defineQuery<string>('orderStatus');
export async function orderWorkflow(orderId: string): Promise<OrderResult> {
let status = 'validating';
let cancelled = false;
setHandler(orderStatusQuery, () => status);
setHandler(cancelOrderSignal, ({ reason }) => {
cancelled = true;
status = `cancelled: ${reason}`;
});
status = 'validating';
const validation = await validateOrder(orderId);
if (!validation.valid) {
return { success: false, reason: validation.reason };
}
if (cancelled) return { success: false, reason: 'Cancelled before reservation' };
status = 'reserving';
let inventoryReserved = false;
try {
await reserveInventory(orderId, validation.items);
inventoryReserved = true;
} catch (e) {
return { success: false, reason: 'Insufficient stock' };
}
if (cancelled) {
await releaseInventory(orderId);
return { success: false, reason: 'Cancelled' };
}
status = 'awaiting_payment';
let paymentId: string | null = null;
setHandler(paymentConfirmedSignal, ({ paymentId: pid }) => {
paymentId = pid;
});
await sleep('30 minutes');
if (!paymentId) {
await releaseInventory(orderId);
return { success: false, reason: 'Payment timeout' };
}
status = 'processing_payment';
try {
await processPayment(orderId, paymentId);
} catch (e) {
await releaseInventory(orderId);
return { success: false, reason: 'Payment failed' };
}
status = 'completed';
await sendConfirmation(orderId);
return { success: true, orderId };
}
Activities и Worker
Activities выполняют реальные операции: HTTP-запросы, запись в БД. Пример валидации заказа:
export async function validateOrder(orderId: string): Promise<ValidationResult> {
const order = await orderRepository.findById(orderId);
if (!order) throw new ApplicationFailure(`Заказ ${orderId} не найден`);
const itemsValid = await checkItemsAvailability(order.items);
return { valid: itemsValid, items: order.items, reason: itemsValid ? null : 'Товары недоступны' };
}
export async function processPayment(orderId: string, paymentId: string): Promise<void> {
const result = await stripeService.capturePayment(paymentId);
if (result.status !== 'succeeded') {
throw new ApplicationFailure(`Оплата не прошла: ${result.failureMessage}`);
}
await orderRepository.markAsPaid(orderId, paymentId);
}
Worker запускает workflow и activity:
import { Worker } from '@temporalio/worker';
import * as activities from './activities';
const worker = await Worker.create({
workflowsPath: require.resolve('./workflows'),
activities,
taskQueue: 'orders',
maxConcurrentActivityTaskExecutions: 50,
maxConcurrentWorkflowTaskExecutions: 50,
});
await worker.run();
Запуск workflow и отправка сигналов
import { Client } from '@temporalio/client';
const client = new Client();
const handle = await client.workflow.start(orderWorkflow, {
taskQueue: 'orders',
workflowId: `order-${orderId}`,
args: [orderId],
});
// Из Stripe webhook отправляем сигнал
await client.workflow.getHandle(`order-${orderId}`)
.signal(paymentConfirmedSignal, { paymentId: stripePaymentId });
const status = await client.workflow.getHandle(`order-${orderId}`)
.query(orderStatusQuery);
console.log('Статус заказа:', status);
Тестирование workflow с Temporal Testing Framework
Для тестирования используйте Temporal Testing Framework. Он позволяет запускать workflow в локальном эмуляторе без внешних зависимостей. Например, можно проверить, что при timeout оплаты срабатывает компенсационное activity. Тесты выполняются за миллисекунды, так как эмулятор ускоряет время. В наших проектах мы покрываем ключевые сценарии юнит- и e2e-тестами, что снижает баги на 90%.
Ключевые концепции
Workflow — детерминированная функция, определяющая порядок шагов. Может «спать» часами/днями, ждать сигналов. Activity — отдельный шаг с side effects (HTTP-запрос, запись в БД). Activities имеют retry-политику. Worker — процесс, который выполняет Workflow и Activity код. Signal — внешнее событие, меняющее состояние workflow (например, «платёж подтверждён»). Query — чтение текущего состояния без изменения. Эти концепции составляют основу любой разработки workflow на Temporal.
Этапы и сроки
| Этап | Длительность |
|---|---|
| Аналитика и проектирование | 2–5 дней |
| Реализация workflow + activities | 1–2 недели |
| Интеграция с существующей инфраструктурой | 1–2 недели |
| Тестирование и отладка | 3–5 дней |
| Документация и обучение | 2–3 дня |
Сроки зависят от сложности процессов. Свяжитесь с нами для бесплатной оценки вашего проекта. Получите консультацию эксперта по Temporal.
При внедрении важно учитывать детерминированность workflow: избегайте недетерминированных функций и прямых сетевых вызовов. Используйте сигналы для долгих ожиданий и версионирование через patched(). Эти принципы помогают избежать типичных ошибок и сделать систему надёжной.
Источник: Официальная документация Temporal







