Как снять бронь товара при отмене заказа
Что проверяют
Умеете ли вы сделать операцию надёжной, а не просто написать UPDATE. Интервьюер ждёт идемпотентности, защиты от гонки с оплатой и понимания, что заказ и склад часто живут в разных сервисах. Следом спрашивают: а если сервис упал между отменой заказа и снятием брони?
Как отвечать
Сначала модель данных. Бронь это отдельная запись со статусом (active, released, consumed), количеством и сроком жизни, а доступный остаток считается как «на складе минус забронировано». Булев флаг «товар зарезервирован» ломается, как только у товара больше одной единицы.
Внутри одного сервиса снятие брони делается одной транзакцией:
- условный переход статуса:
UPDATE reservations SET status = 'released' WHERE order_id = $1 AND status = 'active' RETURNING sku, qty. Условие по статусу защищает от гонки с оплатой и делает повторную отмену безопасной: вторая просто не найдёт строк; - возврат остатка: для каждой строки
UPDATE stock SET reserved = reserved - $2 WHERE sku = $1, SKU обновляются в отсортированном порядке, чтобы две параллельные транзакции не поймали дедлок; - сторона Go:
tx, err := db.BeginTx(ctx, nil)и сразуdefer tx.Rollback(), после успешногоCommitоткат ничего не делает. Контекст с таймаутом пробрасывается во все запросы.
Если заказы и склад в разных сервисах, общей транзакции нет. Сервис заказов в той же транзакции, где меняет статус, пишет событие OrderCancelled в таблицу outbox, отдельный процесс публикует его в Kafka. Доставка at-least-once, поэтому склад хранит обработанные event_id и игнорирует дубли. Страховка на случай потерь: у брони есть expires_at, и фоновая задача освобождает просроченные.
Где спотыкаются
Возвращают ошибку на повторную отмену, потому что брони уже нет. Для потребителя событий это бесконечные ретраи, правильный ответ здесь «уже сделано».
Публикуют событие в брокер после коммита отдельным вызовом. Процесс падает между двумя шагами, заказ отменён, а товар навсегда числится в резерве.
Проверяют статус брони отдельным SELECT без FOR UPDATE, а меняют следующим UPDATE. Между ними успевает пройти оплата, и бронь снимается у уже оплаченного заказа.