0059. Синк каталога по требованию и онбординг-хук подключения кабинета
Копия ADR из репозитория:
backend/docs/adr/0059-catalog-sync-on-demand-and-onboarding-hook.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-09-28
- Связанные задачи: [BE] Catalog: HTTP sync + первичное наполнение SKU при подключении кабинета
Контекст и проблема
Общий каталог товаров (ADR-0052) наполнялся только расписанием: карточки —
cron 04:00 MSK, цены — каждые 30 минут. У только что подключённого кабинета
GET /pricing/products и GET /repricer/products оставались пустыми до утра,
а у пользователя не было кнопки «Синхронизировать».
Два ограничения:
- синк цен пишет только в существующие карточки — на пустом каталоге он бесполезен, пока не прошёл синк карточек;
CatalogModuleимпортируетWbAccountsModule, поэтомуwb-accountsне может импортировать каталог (циклическая зависимость Nest-модулей).@nestjs/event-emitterв проекте нет.
Рассмотренные варианты
- Две джобы (карточки + цены) в одну очередь — просто, но порядок не гарантирован: цены на пустом каталоге ничего не запишут.
- Отдельная джоба «полный синк» — карточки, затем цены в одном прогоне.
- Онбординг:
forwardRefмежду модулями — работает, но закрепляет цикл и делаетwb-accountsзависимым от всех будущих потребителей. - Онбординг: регистрировать очередь каталога в
WbAccountsModule— без цикла модулей, ноwb-accountsзнает про имя очереди и формат джобы каталога. - Онбординг: подписка через
WbAccountsService.onKeyAdded— минимальный in-process хук; потребители подписываются вonModuleInit. - Онбординг: подключить
@nestjs/event-emitter— новая зависимость ради одного события.
Решение
- Джоба
catalog:sync-account-full(вариант 2) в очередиcatalog-sync, исполняетсяCatalogSyncProcessorтеми же импортёрами, что и расписание.jobIdдетерминирован по кабинету — анти-дубль как уPOST /inventory/sync: живая джоба возвращаетalreadyRunning: true, упавшая/завершённая переставляется с полным ретрай-бюджетом. - HTTP:
POST /catalog/sync(202+{ jobId, alreadyRunning }),GET /catalog/sync/status, алиасGET /catalog/products. - Онбординг (вариант 5):
WbAccountsServiceпосле создания кабинета и после смены ключа вызывает подписчиков fire-and-forget — промис не ожидается, ошибки только логируются. Недоступный Redis или зависшийqueue.addне роняет и не задерживаетPOST /wb-accounts.CatalogSyncJobsServiceподписывается вonModuleInitи ставит полный синк в системном контексте.
Последствия
- Положительные: каталог нового кабинета появляется через минуты, а не к утру;
ручной и плановый синк идут одним кодом и одним rate-limit бюджетом WB; хук
переиспользуем (например, первичный синк остатков) без новых зависимостей
у
wb-accounts. - Отрицательные / компромиссы: хук in-process и без гарантии доставки — если
процесс упадёт между сохранением кабинета и постановкой джобы, каталог
наполнит ближайший cron (или кнопка «Синхронизировать»).
stateвGET /catalog/sync/statusотражает только джобу по требованию, не плановые прогоны; свежесть данных —lastSyncAt(последний синк цен каталога). - Дальше: при появлении второго-третьего подписчика — рассмотреть
@nestjs/event-emitterвместо ручного хука.