> Как устроен оркестратор, управляющий задачами для платежей (Go)

Уровень: senior · Роль: backend · Язык: Go · Категория: Технические вопросы

Компании: InDrive

Стек: Go

> Пример ответа

Оркестратор для платежей - это центральный компонент, который координирует выполнение распределённых задач, обеспечивая их надёжность и согласованность. В Go он часто строится на основе паттерна "сага" (Saga) с двумя подходами: хореография (события) или оркестрация (центральный координатор). Я опишу второй, как более типичный для сложных платёжных систем.

Архитектура:

  • API-слой принимает запросы на создание платежа (например, POST /payments).
  • Оркестратор (State Machine) управляет состоянием каждой транзакции. В Go это может быть структура с полем state и методами-обработчиками, которые вызываются последовательно.
  • Очередь задач (например, NATS, RabbitMQ или Redis Streams) для асинхронного выполнения шагов и повторных попыток.
  • Хранилище состояний (PostgreSQL, Redis) для сохранения прогресса и компенсирующих действий.

Ключевые компоненты на Go:

  1. Модель задачи - структура с полями: ID, State, Payload, CompensationActions.
  2. Машина состояний - карта переходов: Init → Authorize → Capture → Complete или Init → Authorize → Fail → Refund. Каждый переход - это функция, которая отправляет задачу в очередь.
  3. Воркеры - горутины, слушающие очередь. Они выполняют шаг (например, вызов API банка) и обновляют состояние в БД. При ошибке - запускают компенсацию (откат).
  4. Retry-механизм - с экспоненциальной задержкой (через time.Ticker или библиотеку cenkalti/backoff).
  5. Идемпотентность - каждый шаг проверяет уникальный ключ (например, paymentID:step) в Redis, чтобы избежать дублей.

Пример упрощённого кода:

GO
type PaymentOrchestrator struct {
db *sql.DB
queue Queue
}
func (o *PaymentOrchestrator) StartPayment(ctx context.Context, paymentID string) error {
// Сохраняем начальное состояние
o.db.Exec("INSERT INTO payments (id, state) VALUES ($1, 'init')", paymentID)
// Отправляем первый шаг в очередь
return o.queue.Publish("payment.steps", StepMessage{PaymentID: paymentID, Step: "authorize"})
}
func (o *PaymentOrchestrator) HandleStep(ctx context.Context, msg StepMessage) error {
// Получаем текущее состояние
state := o.getState(msg.PaymentID)
switch msg.Step {
case "authorize":
err := callBankAPI(msg.PaymentID)
if err != nil {
o.queue.Publish("payment.steps", StepMessage{PaymentID: msg.PaymentID, Step: "refund"})
return o.updateState(msg.PaymentID, "failed")
}
o.updateState(msg.PaymentID, "authorized")
o.queue.Publish("payment.steps", StepMessage{PaymentID: msg.PaymentID, Step: "capture"})
case "capture": // аналогично
}
return nil
}

Надёжность:

  • Все шаги и компенсации идемпотентны.
  • Используется паттерн Outbox (таблица outbox в БД) для гарантированной отправки сообщений в очередь.
  • Мониторинг через метрики (Prometheus) и алерты на зависшие задачи.

Таким образом, оркестратор на Go - это асинхронная машина состояний с очередями, ретраями и компенсациями, что обеспечивает ACID-подобную согласованность в распределённой среде.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью