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

Event Contract: pin.notifications.dispatched

Версионируемый контракт интеграционного события «уведомление доставлено / окончательно не доставлено». Опциональное событие аналитики доставки — единственный дом полезной нагрузки перечислен здесь; consumer-документы (при появлении) ссылаются сюда. Событие несёт только идентификаторы/enum/метки — тела и PII не входят.

Назначение

Даёт потенциальным потребителям (будущий аналитический контекст / dev-stats) факт финального исхода доставки NotificationEntity (../domain/notification.md) без чтения БД notifications: переход в Sent (успех) или терминальный Failed (исчерпан retry). В первой версии прод-потребителей нет (статус draft, ADR-0056: без событий без потребителей) — контракт зафиксирован для будущей аналитики. Публикуется через транзакционный outbox dispatcher-ом/retry-worker-ом.

Metadata

ПолеЗначение
Event typepin.notifications.dispatched
Topicpin.notifications.dispatched.v1
Partitions / key3 партиции (низкий объём, ADR-0056) / key = NotificationId (= AggregateId)
ContractVersion1
Producernotifications: dispatcher (переход Sent) и retry-worker (терминальный Failed)
Статусdraft (нет прод-потребителей; переезд/эволюция без dual-publish)

Событие публикуется через транзакционный outbox атомарно с переходом статуса. Ключ агрегата AggregateId = NotificationId, AggregateType = "Notification"; MessageId (Guid v7) — идемпотентность у потребителя; Version = ContractVersion.

Payload

Дом полей — entity: NotificationEntity («Доменные и интеграционные события»); все поля — скаляры/enum-строки, вложенных моделей и PII нет.

ПолеТипОбязательностьИсточник (NotificationEntity)CompatibilityОписание
NotificationIdGuidДаIdImmutableКлюч агрегата (= AggregateId, Kafka key).
RecipientUserIdGuidДаRecipientUserIdImmutableПолучатель (id, не PII).
TenantIdGuid?НетTenantIdAdditive onlyКомпания получателя (null — инвестор).
Kindstring (enum NotificationKind)ДаKindAdditive onlyВид уведомления.
Channelstring (enum Channel)ДаChannelAdditive onlyКанал доставки.
Statusstring (enum NotificationStatus)ДаStatusAdditive onlyИтог: Sent / Failed (терминальный).
SourceServicestringДаSourceServiceImmutableКонтекст-источник исходного события.
AttemptsintДаAttemptsAdditive onlyЧисло попыток отправки.
LastErrorCodestring?Нетиз LastErrorAdditive onlyБезопасный код ошибки (для Failed); текст/PII не входят.
DispatchedAtDateTimeOffsetДаSentAt/FailedAtImmutableМомент финального исхода.

Тело/заголовок/адрес/Payload уведомления в событие не входят — только исход доставки.

Когда публикуется

УсловиеПереход состоянияТранзакция
Успешная отправка провайдером/in-appPending/Failed → Sentстатус + outbox в одном согласованном изменении
Исчерпан retry (Attempts >= MaxAttempts)Failed (терминально)то же

Промежуточные Failed (будет retry) событие не порождают — только финальные исходы (снижение шума).

Гарантии доставки, порядка и идемпотентности

  • Доставка — at-least-once. Публикация через транзакционный outbox атомарно с переходом статуса: факт не теряется при падении после коммита, но при повторной публикации outbox-а возможна повторная доставка → потребитель обязан дедуплицировать по MessageId (first-seen).
  • Порядок — per-NotificationId. Ключ партиционирования = NotificationId, поэтому события одного уведомления упорядочены в партиции; глобального порядка между разными уведомлениями нет и не требуется (каждый исход — независимый факт аналитики).
  • Эмиссия — не более одного финала на канал-экземпляр. Одно NotificationEntity порождает максимум одно Sent и максимум одно терминальное Failed; переход Failed → Sent (успешный retry) даёт только Sent; промежуточные Failed подавлены. Дубль на стороне брокера/replay-а снимается дедупом по MessageId.

Потребители (consumers)

Consumer-контекстConsumerSide effectsИдемпотентностьЗадержка (SLA)
(будущий аналитический / dev-stats)— (нет прод-потребителей в v1)агрегация доставляемости по Kind/Channel/SourceServicefirst-seen по MessageId

Версионирование и совместимость (реальность SDK)

  • ContractVersion = 1; при несовпадении версии события потребитель логирует warning и продолжает. Топик кодирует версию суффиксом .v1.
  • Additive-only для minor: новые optional-поля/enum-значения — без bump.
  • Breaking (удаление/переименование/смена типа) — ContractVersion = 2 + pin.notifications.dispatched.v2; т.к. статус draft без прод-потребителей — переезд без периода dual-publish.

Безопасность

Событие несёт только идентификаторы, enum и метку — прямого PII нет по построению (адрес/тело/Payload исключены).

ПолеКласс данныхПравило
NotificationId, RecipientUserId, TenantIdInternalinclude (id, не PII)
Kind, Channel, Status, SourceService, LastErrorCodeInternalinclude (enum/код)
Attempts, DispatchedAtInternalinclude

Событие НЕ содержит secrets, tokens, адрес доставки, тело/заголовок уведомления. Доступ к топику — m2m ACL Kafka.

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

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

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