> Как снять бронь товара при отмене заказа (Go)

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

Компании: sferaplatform.ru

Стек: Go

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

При отмене заказа снятие брони товара - это критическая операция, которая должна быть атомарной и устойчивой к сбоям. В Go с типичным стеком (PostgreSQL, Redis) я бы реализовал это через транзакцию в базе данных.

Пример на Go с использованием database/sql:

GO
func CancelOrder(ctx context.Context, orderID int64) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin tx: %w", err)
}
defer tx.Rollback() // откат в случае ошибки
// 1. Получаем товары заказа с блокировкой строк
rows, err := tx.QueryContext(ctx, `
SELECT product_id, quantity
FROM order_items
WHERE order_id = $1 FOR UPDATE
`, orderID)
if err != nil {
return fmt.Errorf("select items: %w", err)
}
defer rows.Close()
var items []struct {
ProductID int64
Quantity int
}
for rows.Next() {
var item struct {
ProductID int64
Quantity int
}
if err := rows.Scan(&item.ProductID, &item.Quantity); err != nil {
return fmt.Errorf("scan item: %w", err)
}
items = append(items, item)
}
// 2. Возвращаем товары на склад (снимаем бронь)
for _, item := range items {
_, err := tx.ExecContext(ctx, `
UPDATE products
SET reserved = reserved - $1
WHERE id = $2 AND reserved >= $1
`, item.Quantity, item.ProductID)
if err != nil {
return fmt.Errorf("unreserve product %d: %w", item.ProductID, err)
}
}
// 3. Меняем статус заказа
_, err = tx.ExecContext(ctx, `
UPDATE orders
SET status = 'cancelled', updated_at = NOW()
WHERE id = $1 AND status = 'active'
`, orderID)
if err != nil {
return fmt.Errorf("update order: %w", err)
}
// 4. Фиксируем транзакцию
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit tx: %w", err)
}
return nil
}

Ключевые моменты:

  • Используем FOR UPDATE для блокировки строк order_items, чтобы избежать гонок.

  • Проверка reserved >= $1 предотвращает уход в минус при повторных отменах.

  • Вся логика в одной транзакции - если что-то пошло не так, изменения откатываются.

  • Если используется Redis для кэша брони, после коммита БД нужно инвалидировать кэш (например, через паттерн cache-aside).

Этот подход гарантирует консистентность данных даже при параллельных запросах.

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

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