Consumer: Referral Events → Notifications (notifications-referral-events)
Маппит события сервиса referral (реферальные связи, привязки лидов) в NotificationKind и ставит
уведомления в очередь доставки. Дом payload — referral/events/; целевой агрегат —
notification.md; общий алгоритм постановки в очередь —
crm-events.md.
Назначение
Consumer group notifications-referral-events уведомляет реферера о создании/закрытии реферальной связи и
о привязке приведённого лида. persist-then-send; кросс-контекстной записи нет.
Owning-сервис и триггер
| Поле | Значение |
|---|---|
| Сервис/контекст | notifications |
| Consumer group | notifications-referral-events |
| Producer | referral |
| ContractVersion | ожидается 1; несовпадение — warn-and-proceed |
Топики и маппинг событий
| Топик | Событие → NotificationKind | Получатель | Urgency |
|---|---|---|---|
pin.referral.bonds.v1 | bond-created → ReferralBondChanged; bond-closed → ReferralBondChanged (Replaced/Revoked) | реферер (владелец связи) | Normal |
pin.referral.lead-attachments.v1 | lead-attach-requested → ReferralLeadAttached | реферер / агент-владелец лида | Normal |
Дом полей payload — referral/events/bond-created.md,
bond-closed.md,
lead-attach-requested.md; поля уведомления —
ReferralNotificationPayload (notification.md).
Контекст события
- Actor = система; tenant — по получателю. Метаданные события (идентификатор сообщения, агрегат-источник, время) доступны из конверта события.
- Идемпотентность: consumer-level по ключу
notifications-referral:{EventType}:{MessageId}; business-level — dedup-unique (INV-NTF-1).
Входные данные / payload события
| Поле payload | Тип | Обяз. | Как используется |
|---|---|---|---|
BondId/LeadId | Guid | Да | id для ReferralNotificationPayload |
ReferrerUserId | Guid | Да | получатель fan-out |
ReferredUserId/ReferredName | Guid?/строка | Нет | → Referred { Id, Name } |
State/Reason | enum/строка | Да | → State (Created/Replaced/Revoked/Attached) |
Алгоритм
Общая логика постановки в очередь — crm-events.md:
- Восстановить контекст события и сверить
ContractVersion(несовпадение — warn-and-proceed); проверить идемпотентность по идентификатору сообщения — дубль пропускается (ack, no-op). - Проверить payload; при нарушении контракта события —
NTF_INVALID_PAYLOAD(в DLQ). - Определить получателей (реферер / агент-владелец лида); применить дедуп; если получателей нет —
no-recipients(ack). - Для каждого получателя определить каналы по виду и срочности, резолвить шаблон и адрес (адрес — мягкая
деградация при отсутствии); применить тихие часы. Если шаблон не резолвится —
NTF_TEMPLATE_NOT_RESOLVED(в DLQ). - Создать уведомление в статусе
Pendingи отрендерить тело; повтор события не создаёт дубль (dedup по ключу fan-out, INV-NTF-1). Уведомление и отметка идемпотентности фиксируются в одном согласованном изменении (persist-then-send). - Успех → 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/skipped-duplicate/no-recipients/enqueued/failed. - Metrics:
notifications_enqueued_total{kind,channel},notifications_consumer_lag_seconds{source=referral}. - Безопасность: system-actor; ФИО приведённого — PII (маска); m2m ACL Kafka.
Кросс-сервисные контракты
Ожидает от referral: at-least-once, устойчивый MessageId, партиционирование по AggregateId
(связь/лид — порядок событий одной связи сохраняется в партиции), ContractVersion; непустые BondId/LeadId
и ReferrerUserId; State (Created/Replaced/Revoked/Attached), опциональные ReferredUserId/ReferredName.
Предоставляет: NotificationEntity(Pending) рефереру (владельцу связи) / агенту-владельцу лида; обратной
записи в referral нет; исходящее событие — только dispatched (dispatcher).
Порядок, версии и неизвестные события
- Порядок.
bond-created→bond-closed(Replaced/Revoked)по одной связи — последовательные факты, каждый даёт своё уведомление; кросс-событийной сверки нет (не source of truth реферальной системы). Повтор и переупорядочивание безопасны (dedup INV-NTF-1). - Неизвестное значение
State(новый под-тип связи/привязки) → не маппится вKind→ignored-event(ack), лог; неизвестный тип события наpin.referral.*.v1→ignored-event(ack). - Версия ниже/выше
1— warn-and-proceed по известным полям; отсутствие обязательного известного поля →NTF_INVALID_PAYLOAD(DLQ).
Постусловие
- Получателю (реферер/агент-владелец лида) по каждому разрешённому каналу создан ровно один
NotificationEntity(Pending); отметка идемпотентности и уведомления — в одном согласованном изменении; offset подтверждён после фиксации. - ФИО приведённого (
ReferredName) доставлено только адресату; в логах маскируется. - Обратной записи в
referralнет.
Acceptance criteria
- Given
bond-created, When consumer обрабатывает, Then рефереру создаётсяNotificationEntity(Pending, Kind=ReferralBondChanged, State=Created). - Given
bond-closedс причиной, When обработка, Then рефереру создаётся уведомлениеReferralBondChangedсоState ∈ {Replaced, Revoked}. - Given
lead-attach-requested, When обработка, Then агенту-владельцу лида создаётсяReferralLeadAttached. - Given повторная доставка того же события, When обработка, Then
skipped-duplicate(ack), дублей нет. - Given неизвестное значение
State, When маппинг, Thenignored-event(ack), уведомления нет.
Обратные ссылки
Автоссылки: где используется этот документ.
- События (2): Event Contract: pin.referral.bond-closed (ReferralBondClosedKafkaEvent), Event Contract: pin.referral.bond-created (ReferralBondCreatedKafkaEvent)
- index.md (1): Notifications Service
Связанные документы
- Целевой агрегат: notification.md; общий алгоритм — crm-events.md; входные контракты — referral/events/; карта — README контекста.
- ADR: 0052, 0056.