Перейти к основному содержимому

Consumer: Profile Events → Notifications (notifications-profile-events)

Назначение

Группа notifications-profile-events слушает события profile (верификация, персоны) и уведомляет пользователя о ходе верификации персональных данных, а рецензента ПИН — о новой заявке (NotificationEntity). persist-then-send; кросс-контекстной записи нет. Адрес/локаль при enqueue снимаются с контактной реплики recipient-contact.md (её ведёт identity-консюмер), а не синхронным lookup. pin.profile.persons.v1PII-free (email/локаль в нём нет), поэтому контакты из него в реплику не поступают: consumer использует его лишь как fallback-источник статуса верификации. Общий алгоритм enqueue — как в crm-events.md.

Owning-сервис и триггер

ПолеЗначение
Сервис/контекстnotifications (Pin.Notifications.Background)
Consumer groupnotifications-profile-events
Обрабатываемые событиятопики pin.profile.*.v1 (таблица ниже)
Producerprofile
ContractVersionожидается 1; несовпадение — warn-and-proceed
Эффект (событие → что меняется)создаются NotificationEntity (Pending) для владельца персоны/рецензента

Топики и маппинг

ТопикСобытие → NotificationKindПолучательUrgency
pin.profile.verification-requests.v1submitted → VerificationSubmitted; completed(approved) → VerificationApproved; completed(rejected) → VerificationRejectedзаявка → рецензент ПИН; решение → владелец персоныNormal
pin.profile.persons.v1смена статуса верификации в person-upsert (если не покрыта verification-requests) → VerificationApproved/VerificationRejected; прочий upsert — игнорвладелец персоныLow

Дом полей payload — profile/events/verification-request-submitted.md, verification-completed.md, person-upserted.md; поля уведомления — VerificationNotificationPayload (notification.md).

Контекст события

  • Из метаданных события доступны MessageId, CreatedAt, Version, AggregateId; actor = system; tenant по получателю.
  • Идемпотентность: consumer-level ключ notifications-profile:{EventType}:{MessageId}; business-level — unique (SourceMessageId, RecipientUserId, Channel, Kind) (INV-NTF-1).

Входные данные / payload события

Поле payloadТипОбяз.Как используется
RequestId/PersonId/UserIdGuidДаполучатель + id для VerificationNotificationPayload
Decision/Statusenumзав.Decision (approved/rejected)
Reasonstring?НетReason (причина отказа)

Алгоритм

Общая логика enqueue (резолв каналов/шаблона/адреса, материализация, согласованность) — как в crm-events.md; дельта источника — фильтрация person-upsert (шаг 1).

  1. Контекст события. Восстановить метаданные и сверить ContractVersion (несовпадение — warn-and-proceed); consumer-идемпотентность по notifications-profile:{EventType}:{MessageId} (дубль → skipped-duplicate, ack); отфильтровать не-уведомляемый upsert персоны (→ ignored-event, ack). Определить Kind по типу события.
  2. Проверка входа. Обязательные идентификаторы непусты; иначе → NTF_INVALID_PAYLOAD (DLQ).
  3. Резолв получателя. Владелец персоны / рецензент ПИН; дедуп; пусто → no-recipients (ack).
  4. Резолв доставки и материализация. По получателю — каналы для Kind/Urgency; шаблон (Kind, Locale, Channel) (нет активного → NTF_TEMPLATE_NOT_RESOLVED, DLQ); адрес и локаль — снапшот из контактной реплики recipient-contact.md (INV-RC-4/5, graceful); тихие часы; создаётся Notification(Pending) с рендером (dedup INV-NTF-1). Отправку выполняет dispatcher; собственных интеграционных событий consumer не публикует.
  5. Согласованность и результат. Уведомления и отметка идемпотентности фиксируются в одном согласованном изменении, offset — после фиксации. Успех/ignored-event/no-recipients → ack; retriable → retry; non-retriable → DLQ.

Диаграмма

Идемпотентность и replay

  • Consumer-level по MessageId; business-level — dedup-unique (INV-NTF-1). Replay: да.

Retry и DLQ

СбойКодRetryDLQ
Инфраструктурныйbackoff + пауза партицииНет
Нарушение payloadNTF_INVALID_PAYLOADНетДа + алерт
Шаблон не резолвитсяNTF_TEMPLATE_NOT_RESOLVEDНетДа + алерт

Наблюдаемость и безопасность

  • Logs: consumed/ignored-event/skipped-duplicate/no-recipients/enqueued/failed (PII маскируется).
  • Metrics: notifications_enqueued_total{kind,channel}, notifications_consumer_lag_seconds{source=profile}.
  • Безопасность: system-actor; Reason отказа — sensitive (наружу только владельцу); m2m ACL Kafka.

Кросс-сервисные контракты

Ожидает от profile: at-least-once, устойчивый MessageId, партиционирование по AggregateId (заявка/персона), ContractVersion; непустые RequestId/PersonId/UserId; для verification-completedDecision (approved/rejected) и опциональный Reason. Топик verification-requestsавторитетный источник уведомлений о верификации; persons.v1 несёт статус верификации лишь как fallback.

Предоставляет: NotificationEntity(Pending) владельцу персоны (решение) и рецензенту ПИН (новая заявка); обратной записи в profile нет; исходящее событие — только dispatched (dispatcher). Контактную реплику этот consumer не ведёт (persons.v1 PII-free); адрес/локаль при enqueue берёт из реплики, ведомой identity.

Порядок, версии и неизвестные события

  • Двойной источник (dedup-риск). Одно и то же решение верификации может прийти и из verification-requests (completed), и из persons.v1 (смена статуса). Это разные сообщения (разные MessageId), поэтому business-dedup по SourceMessageId их не склеит. Во избежание двойного оповещения verification-requests считается авторитетным: изменение статуса верификации в persons.v1, уже покрытое событием заявки, в Kind не маппится → ignored-event (ack).
  • Прочий upsert персоны (без смены статуса верификации) — ignored-event (ack), не DLQ.
  • Неизвестное значение Decision/Status (новый enum) → не маппится в Approved/Rejectedignored-event (ack), лог для разбора; версия ниже/выше 1 — warn-and-proceed, отсутствие обязательного известного поля → NTF_INVALID_PAYLOAD (DLQ).

Постусловие

  • По submitted создано уведомление рецензенту ПИН; по completed — владельцу персоны (approved/rejected) по разрешённым каналам; отметка идемпотентности и уведомления — в одном согласованном изменении; offset подтверждён после фиксации.
  • Reason отказа доставлен только владельцу персоны; в логах маскируется.
  • Обратной записи в profile нет.

Acceptance criteria

  • Given verification-request-submitted, When consumer обрабатывает, Then рецензенту ПИН создаётся NotificationEntity(Pending, Kind=VerificationSubmitted).
  • Given verification-completed(rejected, Reason), When обработка, Then владельцу персоны создаётся уведомление VerificationRejected с Reason, видимым только владельцу.
  • Given person-upserted со сменой статуса верификации, уже покрытой событием заявки, When маппинг, Then ignored-event (ack), дубля оповещения нет.
  • Given person-upserted без смены статуса верификации, When фильтрация, Then ignored-event (ack).
  • Given повторная доставка того же события, When обработка, Then skipped-duplicate (ack), новых уведомлений нет.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы