← Все проекты уровня 14
Уровень 14 · Длинные бизнес-процессыВариант A

Обработка заказа

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

Техническое задание

Процесс и состояния

Создайте REST-сервис долгой обработки заказа. Заказ содержит клиента, позиции, суммы в RUB minor units, версию процесса и состояние. Последовательность: created -> reserving -> reserved -> paying -> paid -> shipping -> shipped; неизвестный результат оплаты переводит в payment_unknown, откуда доступна только сверка. Отказ резерва ведёт в reservation_failed; подтверждённый отказ оплаты запускает release reservation и затем payment_failed; отказ доставки после оплаты переводит в shipping_failed и требует ручного решения, поскольку автоматический возврат денег может быть необратим. Зафиксируйте разрешённые переходы как таблицу, запрещённый переход отвечает 409 invalid_transition. POST /api/v1/orders создаёт заказ с idempotency key; GET /orders/{id} показывает состояние, историю и текущую попытку; POST /orders/{id}/retry повторяет только retryable шаг; POST /orders/{id}/cancel отменяет только до paid. После оплаты отмена автоматически не выполняется.

Действия и адаптеры

Локальный seed создаёт order_customer и order_operator с фиксированными ролями; токен передаётся в Authorization header, оператору доступны только тестовые операции сверки. Локальные имитации склада, платёжного сервиса и доставки поддерживают успех, временную ошибку, постоянный отказ, таймаут и потерянный ответ. Имитация платёжного сервиса предоставляет GET status по неизменному ключу идемпотентности. Timeout или потерянный ответ немедленно переводит оплату в payment_unknown и сначала запускает сверку этим ключом; повтор списания запрещён до подтверждённого статуса. Каждый вызов получает order_id, step_id и постоянный ключ идемпотентности для повторов. Резерв имеет срок 15 минут. Если статус оплаты неизвестен к истечению 15 минут, заказ переходит в payment_unknown и сохраняет резерв до ручной сверки. Уточнение выполняется запросом к имитации платёжного сервиса с прежним idempotency key; подтверждённая оплата продолжает процесс, подтверждённый отказ освобождает резерв, а неразрешённый статус остаётся на ручном решении. Резерв нельзя отпускать при неизвестном исходе оплаты. Успешное списание сохраняет внешний payment reference; повтор шага с тем же ключом имитация внешнего сервиса возвращает прежний результат. Внешние системы не входят в транзакцию БД; намерение и результат фиксируются отдельно. Если ответ потерян, повтор отправляется с тем же ключом и сверяется по reference. После оплаченного состояния возврат требует отдельной ручной команды и отдельной операции; её состояние не смешивается с отменой заказа.

Долговечность и повторы

PostgreSQL, ручной SQL, миграции, workflow_steps, outbox и audit обязательны. Переход состояния и создание записи следующего шага фиксируются одной транзакцией. Один исполнитель получает право обработки шага на 30 секунд, продлевает его каждые 10 секунд и обрабатывает не более 20 шагов одновременно. После истечения права обработки шаг может получить другой исполнитель. Первая попытка выполняется сразу, затем временная ошибка допускает четыре повтора через 1, 2, 4 и 8 секунд; всего не более 5 попыток на шаг; постоянные ошибки сразу требуют ручного разрешения. Проверка поколения с монотонным номером попытки не даёт прежнему исполнителю сохранить результат после потери аренды. События состояния дедуплицируются по event_id. Контекст и timeout 3 секунды применяются к SQL и имитациям HTTP-сервисов.

Структура программы

Предметный пакет задаёт переходы и компенсации; хранилище фиксирует этапы и повторы, исполнитель запускает их, а отдельные адаптеры вызывают имитации сервисов. main.go только загружает конфигурацию, связывает зависимости и управляет запуском. Пакеты без циклов, общих utils и интерфейсов без потребителя. README показывает зависимости; тесты ядра без HTTP/БД, интеграционные отдельно.

Приёмка

Имитации и фиксированное время проверяют переходы, потерянный ответ после оплаты, истечение резерва, отказ доставки, повторы и конкуренцию двух исполнителей. Остановки покрывают момент до внешнего вызова, после него до фиксации и после фиксации. Критерии: переходы следуют таблице; один ключ не создаёт второе списание; после paid обычная отмена запрещена; право обработки с проверкой поколения исключает двойную запись, все компенсации видны в аудите.

Критерии готовности

Ожидаемый результат

Проверяемый результат

  • README показывает границы и направление зависимостей; main.go только связывает конфигурацию и запуск; модульные тесты ядра обходятся без HTTP и БД, интеграционные тесты отделены.
  • Локальный сервис с PostgreSQL показывает историю состояний заказа и запускает весь процесс через имитации склада, оплаты и доставки.
  • Потерянный ответ повторяется с прежним ключом; имитация платёжного сервиса содержит одно списание и одно reference.
  • Пять ограниченных повторов временной ошибки и немедленный manual-needed статус постоянной ошибки видны по API.
  • Ошибка доставки после оплаты не выдаёт автоматическую отмену или возврат; операция требует отдельного решения.
  • Два исполнителя не фиксируют один шаг после потери права обработки; fencing отвергает результат устаревшей попытки.
  • Офлайн-сценарии проверяют остановки вокруг внешнего вызова, повтор команды, истёкший резерв и атомарность audit/step.
  • README содержит таблицу переходов, обратимые миграции, режим запуска двух исполнителей и доказательство сценариев восстановления с имитациями.