Как гарантировать, что заказ не будет выполнен без исполнителя после резерва товара
Что проверяют
Умеете ли вы думать о заказе как о процессе из нескольких шагов, который может оборваться посередине. Резерв товара и назначение исполнителя обычно живут в разных сервисах, и одной транзакцией их не связать. Следом спрашивают, что будет, если сервис упал между шагами.
Как отвечать
Сначала зафиксируйте инвариант: заказ не уходит в работу без исполнителя, а резерв без исполнителя не висит вечно. Дальше слои защиты:
- машина состояний: явные статусы
reserved,assigned,in_progress,doneи переход условным запросомUPDATE orders SET status = 'in_progress' WHERE id = $1 AND status = 'assigned' AND executor_id IS NOT NULL. В Go проверяетеRowsAffected(): ноль значит, что переход запрещён или его уже сделал другой обработчик; - ограничение в базе:
CHECK (status NOT IN ('in_progress', 'done') OR executor_id IS NOT NULL). Даже баг в коде не запишет заказ в работе без исполнителя; - сага с компенсацией: оркестратор ведёт шаги «резерв, назначение, старт» и, если исполнитель не найден, запускает компенсацию, то есть снятие резерва. Шаги и компенсации идемпотентны, их можно безопасно повторить;
- таймаут резерва: колонка
reserved_untilи фоновый воркер, который забирает просроченные резервы черезSELECT ... FOR UPDATE SKIP LOCKED. Так несколько реплик не обработают один заказ дважды; - transactional outbox: смена статуса и событие для других сервисов пишутся в одной транзакции, отдельный процесс публикует их в брокер. Иначе сервис упадёт после коммита, но до отправки события.
Во все вызовы передавайте context.Context с дедлайном, чтобы зависший сервис назначения не держал заказ бесконечно.
Где спотыкаются
Защищают заказ через sync.Mutex. Он работает внутри одного процесса, а сервис запущен в нескольких репликах.
Предлагают двухфазный коммит между сервисами. Это дорого, брокеры его толком не поддерживают, а при сбое координатора ресурсы остаются заблокированными.
Забывают про время: таймаута на резерв нет, и товар под заказом без исполнителя неделями недоступен другим покупателям.