О проекте
В рамках тестового задания требовалось собрать магазин цифровых товаров с каталогом, оформлением заказа и оплатой. Я расширил исходный сценарий до эксплуатационного: добавил идемпотентный inbox webhook, конкурентно безопасную выдачу ключей, повтор этапов и наблюдаемые состояния ошибок.
Тестовое задание реализовано как полный контур цифрового магазина: Angular-витрина, NestJS API, PostgreSQL через Prisma, webhook оплаты, два поставщика ключей и фоновый worker выдачи. В интерфейсе можно пройти покупку, повторить событие оплаты и проверить восстановление заказа после нехватки товара или сбоя поставщика.
Что отличает реализацию
Выдача выполняется однократно даже при повторных и параллельных webhook. Стабильный provider request ID переживает неоднозначный timeout; out_of_stock и delivery_failed восстанавливаются повторным worker-проходом. Ограничения БД и выборка FOR UPDATE SKIP LOCKED удерживают инварианты на уровне хранилища, а не только контроллера.
Состав прикладного кода
Доли по размеру прикладных файлов Git в закреплённой версии. Каждый файл отнесён к одной подсистеме по правилам scripts/generate-stack-share.mjs; тесты, ассеты, зависимости и generated-файлы исключены. Округление — до 0,1%. Это состав кода, а не уровень владения технологиями.
- Оплата и поставщики — явные моки; деньги не списываются, коды учебные.
- До 20 заказов на сессию и 1000 новых демонстрационных заказов суммарно. TTL один час, очистка каждые пять минут.
- Пример восстановления намеренно завершает первую попытку одного заказа ошибкой. Он не меняет глобальные режимы поставщиков.
Скриншоты, схемы и исходные материалы сохранены на языке оригинала; подписи и описание переведены.
Архитектура и схемы
- Цена и скидка вычисляются на сервере и фиксируются в заказе.
- Idempotency-Key защищён уникальным индексом; входные данные повтора сверяются с исходным намерением.
- Webhook сначала сохраняется в durable inbox. Отдельный worker обрабатывает платёж и очередь выдачи.
- Стабильный requestId поставщика сохраняется при повторе. Неоднозначный timeout не разрешает немедленно выдать второй код.
Code-map: файлы и связи
Карта привязана к версии 767226bf3c2f. В таблице — экран или API, связанный модуль и данные.
| Функция | Экран / API | Модуль | Данные / проверка |
|---|---|---|---|
| Покупка | Storefront / POST orders | storefront.component; ShopService.createOrder | orders, products, promocodes; race tests |
| Оплата | POST payments/simulate → webhooks/payment | PaymentsController; acceptWebhook | payment_events; повтор event_id |
| Выдача | Order / GET orders/:id | worker.service processDeliveryJob | delivery_jobs, provider_requests, fulfillments; recovery |
| Сессия | Guard всех публичных заказов | portfolio-demo PortfolioGuard | HMAC cookie, prefix owner; tests/portfolio/run.ts |
| Сброс / TTL | POST demo/reset / timer | PortfolioCleanup.clear | только portfolio orders; ownership / expiry tests |
Как работать с картой
- Перед изменением найти функцию и пройти связи до API, состояния и хранилища.
- Проверить входы, ошибки, повторные события и зависимые сценарии.
- После изменения обновить карту и затронутые схемы вместе с кодом.
- Выполнить связанные проверки и привязать новые материалы к версии исходников.
Сценарии и API
- Выберите товар и выполните успешную тестовую оплату. Дождитесь кода и просмотрите историю.
- Повторите то же платёжное событие: код и выдача должны остаться единственными.
- Создайте заказ через сценарий сбоя, оплатите и нажмите «Восстановить мой заказ».
- Проверьте отказ оплаты, чужой UUID в другой сессии и сброс своего примера.
Проверка опубликованной версии
CI: CI ↗ · 767226bf · версия этого демо · 2026-09-07T00:49:05Z
CI: Portfolio immutable image ↗ · 767226bf · версия этого демо · 2026-09-07T00:49:05Z
- Real HTTP webhook, durable inbox, worker, single fulfillment preserved on event replay
- Foreign GET/recover return 404; other session reset preserves first order
- Payment failure; isolated first-attempt delivery fault; own recovery succeeds
Эти проверки относятся к указанной версии. Коммерческие показатели и качество моделей на независимой выборке здесь не измерялись.
Измерения
- Сохранено 14 содержательных кадров фактического интерфейса.
- Дата, сценарий и версия указаны у каждого кадра.
- Максимум RAM контейнеров в коротком тесте: 218.56 MiB. Общие службы учитываются отдельно; магазин включает свою БД.
- Измерено 6 повторений сценария; медиана 129.0 мс, максимум 1939 мс. Время включает браузер и HTTPS; методика и ограничения приложены.
Запуск и эксплуатация
У демки и исходного приложения одна модель заказов. Демо-guard включается через PORTFOLIO_DEMO=true. Без этого флага действуют исходные контракты API.
Сборка выполняется GitHub Actions из закреплённого commit SHA. На VPS загружается готовый GHCR-образ. База магазина остаётся отдельной от новых приложений.
Запуск: docker compose --env-file .env.production -f compose.production.yaml up -d. До переключения выполнить prisma migrate deploy. Секреты хранятся только на сервере. Seed нельзя использовать как безусловный сброс данных работающего магазина.
Readiness: /api/health/ready проверяет PostgreSQL. Во время инцидента сравнить историю заказа, inbox и delivery job; повторять выдачу по исходному orderId, не создавать новую покупку.
Сборка вне VPS, на сервер уходят готовые файлы и образы с фиксированной версией. Секреты отдельно. Логи ротируются; после изменения log options контейнер нужно пересоздать. Общий docker prune не используется.
Code-map связывает экран или API с модулем и данными. При правке нужно проверить эти связи, обновить карту и прогнать затронутые сценарии: обычный проход, ошибку, повтор, сброс. Схема может устареть, поэтому её версия указана рядом.
Откат проверен 2026-09-07T01:07:46.255650+00:00: 20260907-verified → 20260907 → 20260907-verified. После каждого переключения подтверждены HTTPS, readiness и SHA. Это проверка конфигурации и артефактов на одном проверенном SHA; обратная миграция БД не выполнялась.
Стек
| Angular / TypeScript | Витрина, сигналы состояния, HTTP-клиент и polling заказа. |
|---|---|
| NestJS | Контракты, валидация, webhook, операции восстановления и границы демо. |
| PostgreSQL / Prisma | Снимки денег, inbox, очередь с lease, уникальные индексы и транзакции. |
| Node.js worker | HTTP-вызовы поставщиков вне короткой транзакции, retry и финальная выдача. |
Материалы и исходники
История реализации: 1–3 сентября 2026. Подготовка портфолио: 7 сентября 2026.
Архив материалов на языке оригинала.