Consumer: Profile Events → Notifications (notifications-profile-events)
Назначение
Группа notifications-profile-events слушает события profile (верификация, персоны) и уведомляет
пользователя о ходе верификации персональных данных, а рецензента ПИН — о новой заявке
(NotificationEntity). persist-then-send; кросс-контекстной записи нет. Адрес/локаль
при enqueue снимаются с контактной реплики recipient-contact.md (её ведёт
identity-консюмер), а не синхронным lookup. pin.profile.persons.v1 — PII-free (email/локаль в нём нет),
поэтому контакты из него в реплику не поступают: consumer использует его лишь как fallback-источник статуса
верификации. Общий алгоритм enqueue — как в crm-events.md.
Owning-сервис и триггер
| Поле | Значение |
|---|---|
| Сервис/контекст | notifications (Pin.Notifications.Background) |
| Consumer group | notifications-profile-events |
| Обрабатываемые события | топики pin.profile.*.v1 (таблица ниже) |
| Producer | profile |
| ContractVersion | ожидается 1; несовпадение — warn-and-proceed |
| Эффект (событие → что меняется) | создаются NotificationEntity (Pending) для владельца персоны/рецензента |
Топики и маппинг
| Топик | Событие → NotificationKind | Получатель | Urgency |
|---|---|---|---|
pin.profile.verification-requests.v1 | submitted → 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/UserId | Guid | Да | получатель + id для VerificationNotificationPayload |
Decision/Status | enum | зав. | → Decision (approved/rejected) |
Reason | string? | Нет | → Reason (причина отказа) |
Алгоритм
Общая логика enqueue (резолв каналов/шаблона/адреса, материализация, согласованность) — как в crm-events.md; дельта источника — фильтрация person-upsert (шаг 1).
- Контекст события. Восстановить метаданные и сверить
ContractVersion(несовпадение — warn-and-proceed); consumer-идемпотентность поnotifications-profile:{EventType}:{MessageId}(дубль →skipped-duplicate, ack); отфильтровать не-уведомляемый upsert персоны (→ignored-event, ack). ОпределитьKindпо типу события. - Проверка входа. Обязательные идентификаторы непусты; иначе →
NTF_INVALID_PAYLOAD(DLQ). - Резолв получателя. Владелец персоны / рецензент ПИН; дедуп; пусто →
no-recipients(ack). - Резолв доставки и материализация. По получателю — каналы для
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 не публикует. - Согласованность и результат. Уведомления и отметка идемпотентности фиксируются в одном согласованном
изменении, 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
| Сбой | Код | Retry | DLQ |
|---|---|---|---|
| Инфраструктурный | — | backoff + пауза партиции | Нет |
| Нарушение payload | NTF_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-completed —
Decision (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/Rejected→ignored-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 маппинг, Thenignored-event(ack), дубля оповещения нет. - Given
person-upsertedбез смены статуса верификации, When фильтрация, Thenignored-event(ack). - Given повторная доставка того же события, When обработка, Then
skipped-duplicate(ack), новых уведомлений нет.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: NotificationEntity
- События (1): Event Contract: pin.profile.person-upserted (PersonUpsertedKafkaEvent)
- index.md (1): Notifications Service
Связанные документы
- Целевой агрегат: notification.md; контактная реплика — recipient-contact.md; общий алгоритм — crm-events.md; входные контракты — profile/events/; карта — README контекста.
- ADR: 0052, 0056.