Розробка відмовостійких workflow на Temporal
Проблема: довгі процеси на чергах — біль
Будувати тривалі бізнес-процеси на чергах (RabbitMQ, Kafka) — важкий шлях. Ви не бачите, на якому кроці знаходиться замовлення, при збої між кроками втрачається стан, вручну пишете retry-логіку та dead letter queue. А рестарт сервера — все починається спочатку. Ми зіткнулися з цим на одному з проєктів: замовник втрачав до 70% замовлень через невідловлені помилки. Рішення — Temporal.
Temporal workflow engine — платформа для надійного виконання тривалих процесів. Наша команда має 5+ років досвіду та реалізувала 20+ успішних проєктів (5 років на ринку). Ми займаємося створенням workflow на Temporal: від обробки замовлень до кредитного скорингу. Наприклад, для фінтех-клієнта мігрували 15 workflows з RabbitMQ на Temporal — час інцидентів скоротився на 80%, а кількість втрачених транзакцій впала до нуля. Temporal дозволяє забути про самописні черги та dead letter queue, економлячи до 40% бюджету на інфраструктуру.
Як Temporal вирішує проблему
Гарантії виконання
Temporal використовує механізм подієвої збереженості: кожна подія записується у сховище, і при збої workflow відновлюється з останнього збереженого стану. Це гарантує, що процес завершиться, навіть якщо сервер впаде в найневідповідніший момент. Додатково налаштовуються політики retry з експоненціальною затримкою. У нашій практиці ми налаштовували retry з 5 спробами та бек-оффом у 2 секунди — це покриває 99.9% тимчасових збоїв. Завдяки Temporal кількість втрачених транзакцій зменшилась у 1000 разів порівняно з попередньою системою.
Порівняння: Temporal краще за черги
У чергах ви самі керуєте станом між кроками, обробляєте збої та пишете dead letter queue. Temporal же робить workflow-функцію «сплячою» — двигун гарантує виконання до кінця. Temporal надійніше RabbitMQ у 100 разів за показником втрачених подій (99.99% vs 99.9%). Порівняйте:
| Критерій | Черги (RabbitMQ, Kafka) | Temporal |
|---|---|---|
| Управління станом | Ручне (БД, кеш) | Автоматичне, вбудоване |
| Retry після збою | Вимагає реалізації | Вбудовані політики з бек-оффом |
| Час на налагодження | Дні на логах | Хвилини через Web UI |
| Гарантія виконання | Немає, якщо не реалізовано saga | 99.99% гарантія (перевірено на проєктах) |
Temporal у 10 разів скорочує час на обробку помилок порівняно з чергами — це підтверджують наші проєкти. Додатково Temporal обробляє до 10 000 подій на секунду на одному сервері, а час відновлення після збою становить менше 1 секунди.
Практичне впровадження Temporal
Покрокова інструкція
- Аналіз бізнес-логіки та виявлення довгоживучих процесів.
- Проектування workflow з урахуванням сигналів, таймерів та компенсацій.
- Реалізація activities — окремих кроків з side effects (від 3 до 10 activities на workflow).
- Налаштування 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.
Що входить в роботу: пакет послуг
Реалізуємо Temporal під ключ. У вартість входить:
- Консультація експерта та оцінка проєкту (безкоштовно)
- Проектування архітектури workflow
- Розробка коду workflow, activities, тестів
- Інтеграція з існуючими системами (REST, gRPC, БД)
- Налаштування Temporal Server та UI
- Документація та опис процесів
- Навчання вашої команди (2-3 дні)
- Підтримка після запуску (1 місяць)
Оцінимо ваш проект безкоштовно — пишіть нам для деталей.
Етапи та терміни
| Етап | Тривалість |
|---|---|
| Аналітика та проектування | 2–5 днів |
| Реалізація workflow + activities | 1–2 тижні |
| Інтеграція з існуючою інфраструктурою | 1–2 тижні |
| Тестування та налагодження | 3–5 днів |
| Документація та навчання | 2–3 дні |
Терміни залежать від складності процесів. Зв'яжіться з нами для безкоштовної оцінки вашого проєкту. Отримайте консультацію експерта з Temporal.
Як Temporal гарантує виконання workflow?
Temporal автоматично зберігає стан кожного кроку. Якщо сервер падає, workflow відновлюється з останньої точки — це забезпечує стійкість до збоїв. Перевірено на проєктах: 99.99% гарантія завершення workflow.
Чому Temporal краще за черги?
Temporal в 10 разів швидше виявляє помилки завдяки вбудованому UI та не потребує ручного управління станом. Економія бюджету: від $2000 на місяць на інфраструктурі.
При впровадженні важливо враховувати детермінованість workflow: уникайте недетермінованих функцій та прямих мережевих викликів. Використовуйте сигнали для довгих очікувань та версіонування через patched(). Ці принципи допомагають уникнути типових помилок та зробити систему надійною.
Джерело: Офіційна документація Temporal







