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

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-апрув.

Состояния: startedfinish / 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 → PROCESSING → PAYMENT_COMPLETED → PAYMENT_CONFIRMED → SUCCESS

Таймауты: - CREATEDACCEPTED: 20 минут (если покупатель не принял — отмена) - ACCEPTEDPROCESSING: 20 минут (если не началась обработка — отмена)

Ветка диспутов:

(любое состояние после ACCEPTED) → DISPUTE_START → DISPUTE_DECISION_AWAITING → DISPUTE_DECIDED

Сигналы воркфлоу:

Сигнал Описание
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/ доступны всем воркерам. Каждый воркер регистрирует только те активити, которые ему нужны, но реализация общая. Это позволяет избежать дублирования кода при схожей логике в разных сервисах.