Temporal.io — оркестрация бизнес-процессов¶
Обзор¶
Temporal.io используется в 3 сервисах монорепозитория Univex: univex-id, ledger, p2p. Каждый сервис имеет свой task queue и запускает отдельный worker-процесс, который регистрирует собственный набор воркфлоу и активитей.
| Сервис | Task Queue | Воркфлоу |
|---|---|---|
| univex-id | univex-id-task-queue |
8 |
| ledger | ledger-task-queue |
7 |
| p2p | p2p-task-queue |
1 (сложный) |
Temporal server: задаётся через конфигурацию (TEMPORAL_HOST)
Архитектура¶
Shared infrastructure¶
Общая инфраструктура вынесена в internal/temporal/ и доступна всем воркерам монорепозитория:
internal/temporal/activities/— 36+ модулей активитей (уведомления, верификация, работа с exchange, управление аккаунтами, операции с балансом и т.д.)internal/temporal/workflows/— 10 типов воркфлоуinternal/temporal/common/signals.go— общие сигналы подтвержденийinternal/temporal/domains/— доменные структуры
Shared pkg¶
Пакет pkg/temporal/ предоставляет базовые дженерик-типы для всех воркфлоу и активитей:
WorkflowBase[IN, OUT]— базовый тип воркфлоу с типизированным входом и выходомActivityBase[IN, OUT]— базовый тип активити- Interceptors — middleware для логирования, трассировки, обработки ошибок
Топология¶
┌─────────────────────────────────────────────────────────────┐
│ Temporal Server │
│ (TEMPORAL_HOST:7233) │
└────────────┬──────────────────┬──────────────────┬──────────┘
│ │ │
┌────────▼───────┐ ┌────────▼───────┐ ┌────────▼───────┐
│ univex-id │ │ ledger │ │ p2p │
│ worker │ │ worker │ │ worker │
│ │ │ │ │ │
│ task queue: │ │ task queue: │ │ task queue: │
│ univex-id- │ │ ledger- │ │ p2p- │
│ task-queue │ │ task-queue │ │ task-queue │
└────────────────┘ └────────────────┘ └────────────────┘
│ │ │
┌────────▼──────────────────▼──────────────────▼──────────┐
│ internal/temporal/activities/ │
│ 36+ shared modules │
└─────────────────────────────────────────────────────────┘
Воркфлоу по сервисам¶
univex-id (8 воркфлоу)¶
1. signup — регистрация пользователя¶
Timeout: 1 час
Шаги: 1. Создание операции регистрации 2. Ожидание подтверждений: email / phone / 2FA (signal-driven) 3. Создание аккаунта 4. Создание сессии 5. Генерация exchange-аккаунта
2. signin — вход в систему¶
Timeout: 1 час
Шаги: 1. Получение доступных auth-методов пользователя 2. Создание операции входа 3. Ожидание подтверждений (signal-driven) 4. Создание сессии 5. Генерация exchange-токена
3. ban — блокировка аккаунта¶
Шаги: 1. Обновление статуса пользователя 2. Обновление статуса в exchange 3. Создание события бана
4. unban — разблокировка аккаунта¶
Шаги: 1. Обновление статуса пользователя 2. Обновление статуса в exchange 3. Создание события разбана
5. credentials_changed_ban — автобан при смене пароля¶
Шаги:
1. Установка статуса CREDENTIALS_CHANGED
2. Сон 24 часа
3. Автоматический разбан
6. create_api_key — создание API-ключа¶
Шаги: 1. Проверка 2FA 2. Проверка лимита: не более 5 ключей на аккаунт 3. Создание ключа в exchange
7. transfer — внутренний перевод¶
Шаги: 1. Проверка баланса 2. Создание операции перевода 3. Ожидание подтверждения 2FA — таймаут 10 минут 4. Выполнение перевода
8. withdrawal — вывод средств¶
Наиболее сложный воркфлоу univex-id. Реализован как явная state machine с 8 состояниями и таймаутами до 72 часов на admin-апрув.
Состояния: started → finish / softLimitExceeded / hardLimitExceeded / riskTooHigh / blacklisted / failed / completed
stateDiagram-v2
[*] --> started
started --> softLimitExceeded : мягкий лимит превышен
started --> hardLimitExceeded : жёсткий лимит превышен
started --> riskTooHigh : высокий риск
started --> blacklisted : адрес в чёрном списке
started --> finish : лимиты в норме
softLimitExceeded --> finish : admin апрув (до 72 ч)
softLimitExceeded --> failed : admin отказ / таймаут
hardLimitExceeded --> finish : admin апрув (до 72 ч)
hardLimitExceeded --> failed : admin отказ / таймаут
riskTooHigh --> finish : confirm_withdraw_high_risk (сигнал)
riskTooHigh --> failed : таймаут
blacklisted --> failed : немедленно
finish --> completed : транзакция выполнена
finish --> failed : ошибка выполнения
completed --> [*]
failed --> [*]
ledger (7 воркфлоу)¶
1. withdrawal — вывод средств¶
Аналог withdrawal в univex-id, адаптированный для типов аккаунтов ledger. Та же state machine, но работает с ledger-балансами.
2. transfer — внутренний перевод¶
Надёжный внутренний перевод между пользователями: - Списание у отправителя - Зачисление у получателя - Компенсация при сбое на любом шаге - Ожидание 2FA: таймаут 10 минут
3. fiat — фиатные операции¶
Signal-driven воркфлоу. Таймаут 24 часа на каждый шаг.
Сигналы по порядку:
1. AcceptFiatBid — кассир принял заявку
2. ProcessFiatBid — заявка в обработке
3. FundsIssued — средства выданы/получены
4. freeze_money — заморозка активов¶
Шаги: 1. Проверка достаточности баланса 2. Списание суммы с активного баланса 3. Создание записи frozen asset
5. unfreeze_money — разморозка активов¶
Шаги: 1. Получение суммы замороженного актива 2. Возврат суммы на активный баланс 3. Удаление записи frozen asset
6. spend_frozen_assets — трата замороженных активов¶
Шаги: 1. Проверка наличия замороженного актива 2. Удаление записи (средства не возвращаются на баланс)
7. manual_operation — ручная операция администратора¶
Шаги: 1. Верификация прав администратора 2. Отправка уведомления 3. Выполнение операции
p2p (1 воркфлоу)¶
order — жизненный цикл P2P-ордера¶
Наиболее сложный воркфлоу p2p-сервиса. Описывает полный жизненный цикл сделки от создания до завершения, включая ветку диспутов.
Основной поток:
Таймауты:
- CREATED → ACCEPTED: 20 минут (если покупатель не принял — отмена)
- ACCEPTED → PROCESSING: 20 минут (если не началась обработка — отмена)
Ветка диспутов:
Сигналы воркфлоу:
| Сигнал | Описание |
|---|---|
order_accepted |
Покупатель принял ордер |
payment_complete |
Покупатель подтвердил оплату |
payment_confirmed |
Продавец подтвердил получение средств |
order_canceled |
Ордер отменён одной из сторон |
dispute |
Инициирован диспут |
stateDiagram-v2
[*] --> CREATED
CREATED --> ACCEPTED : order_accepted (до 20 мин)
CREATED --> CANCELLED : таймаут / order_canceled
ACCEPTED --> PROCESSING : (до 20 мин)
ACCEPTED --> CANCELLED : таймаут / order_canceled
PROCESSING --> PAYMENT_COMPLETED : payment_complete
PROCESSING --> DISPUTE_START : dispute
PAYMENT_COMPLETED --> PAYMENT_CONFIRMED : payment_confirmed
PAYMENT_COMPLETED --> DISPUTE_START : dispute
PAYMENT_CONFIRMED --> SUCCESS
DISPUTE_START --> DISPUTE_DECISION_AWAITING
DISPUTE_DECISION_AWAITING --> DISPUTE_DECIDED : решение администратора
DISPUTE_DECIDED --> SUCCESS : в пользу покупателя
DISPUTE_DECIDED --> CANCELLED : в пользу продавца
SUCCESS --> [*]
CANCELLED --> [*]
Сигналы подтверждений¶
Общие сигналы определены в internal/temporal/common/signals.go и используются воркфлоу univex-id и ledger для ожидания внешних подтверждений.
| Сигнал | Описание |
|---|---|
complete_2fa_confirmation |
Подтверждение двухфакторной аутентификации |
complete_email_confirmation |
Подтверждение по электронной почте |
complete_phone_confirmation |
Подтверждение по телефону |
complete_admin_confirmation |
Подтверждение администратором |
confirm_withdraw_high_risk |
Подтверждение вывода с высоким риском |
Воркфлоу блокируется на workflow.GetSignalChannel(...).Receive(ctx, &payload) до получения сигнала или истечения таймаута.
Retry-политики¶
Default retry policy применяется ко всем активитям:
| Параметр | Значение |
|---|---|
InitialInterval |
100ms |
BackoffCoefficient |
2.0 |
MaximumInterval |
1s |
MaximumAttempts |
5–10 (зависит от активити) |
Для критичных финансовых операций (списание, зачисление) используется MaximumAttempts=1 с явной компенсацией на уровне воркфлоу.
Паттерны¶
1. Workflow ID = Operation ID¶
Идемпотентность обеспечивается через использование UUID операции как Workflow ID. Повторный запуск воркфлоу с тем же ID вернёт уже существующий воркфлоу без дублирования.
workflowOptions := client.StartWorkflowOptions{
ID: operationID.String(),
TaskQueue: "univex-id-task-queue",
}
2. Signal-driven workflows¶
Воркфлоу ожидает внешних событий (подтверждение пользователя, действие кассира) через сигналы с явными таймаутами:
signalCh := workflow.GetSignalChannel(ctx, signals.Complete2FAConfirmation)
selector := workflow.NewSelector(ctx)
selector.AddReceive(signalCh, func(ch workflow.ReceiveChannel, more bool) {
ch.Receive(ctx, &result)
})
selector.AddFuture(timerFuture, func(f workflow.Future) {
// таймаут — отмена операции
})
selector.Select(ctx)
3. State machine¶
Явное отслеживание состояний через switch и рекурсию/цикл. Воркфлоу withdrawal в univex-id переходит между состояниями, проверяя условия на каждом шаге:
switch state {
case StateStarted:
// проверка лимитов, рисков, блэклиста
case StateSoftLimitExceeded:
// ожидание admin-апрува
case StateFinish:
// выполнение транзакции
}
4. Cross-service signals¶
API-обработчик одного сервиса отправляет сигнал в воркфлоу другого сервиса. Например, p2p отправляет сигнал в ledger-воркфлоу заморозки при завершении ордера:
// p2p handler → ledger workflow
err = temporalClient.SignalWorkflow(ctx, workflowID, "", "spend_frozen_assets", payload)
5. Shared activities¶
36+ модулей активитей в internal/temporal/activities/ доступны всем воркерам. Каждый воркер регистрирует только те активити, которые ему нужны, но реализация общая. Это позволяет избежать дублирования кода при схожей логике в разных сервисах.