Event Contract: pin.messenger.contact-request-accepted (ContactRequestAcceptedEvent)
Версионируемый контракт интеграционного события «заявка в контакты подтверждена». Consumer-документы
(notifications) ссылаются сюда, не переописывая payload. Событие фиксирует факт подтверждения связи
(ContactStatus: Pending → Accepted, INV-CT-3/5) — контакт стал двусторонним: обе стороны получают полный
профиль и право звонка. notifications уведомляет инициатора о подтверждении без синхронного запроса
в messenger. Дом полей связи — contact.md; право звонка —
call-session.md.
Назначение
При Accept заявка Pending становится Accepted: обе стороны видят полный профиль друг друга (ФИО +
телефон, messenger-user.md) и получают право звонка
(call-session.md, INV-CALL-1). Событие даёт notifications триггер уведомить
инициатора (RequesterUserId) о подтверждении (offline push/email); in-app push обеим сторонам
(ContactRequestAccepted в hub) идёт отдельно (persist-then-push). Разблокировка звонков —
внутридоменный эффект messenger (guard INV-CT-5 над CallSession), а не отдельный consumer. PII
(ФИО/телефон) в payload не входит — резолв у consumer-а из реплики identity.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.messenger.contact-request-accepted |
| Класс события | ContactRequestAcceptedEvent |
| Topic | pin.messenger.contact-request-accepted.v1 |
| Partitions / key | 3 / key = ContactId (порядок событий per-связи) |
| ContractVersion | 1 |
| Producer | messenger: подтверждение заявки (accept-contact-request, дом contact.md) |
| Статус | draft |
Событие публикуется атомарно (в одном согласованном изменении) с переходом Pending → Accepted через
транзакционный outbox. Партиционирование по ключу ContactId кладёт событие в один раздел со своим
contact-request-created — порядок жизненного цикла связи.
Payload ContactRequestAcceptedEvent
Корреляционные поля; ФИО/телефон сторон не входят (PII — резолв у consumer-а из реплики identity).
Дом полей — contact.md.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
ContactId | Guid | Да | Contact.Id | Immutable | Связь (Kafka key). Бизнес-ключ идемпотентности. |
RequesterUserId | Guid | Да | Contact.RequesterUserId | Immutable | Инициатор заявки — получатель уведомления о подтверждении. |
AddresseeUserId | Guid | Да | Contact.AddresseeUserId | Immutable | Адресат, подтвердивший связь. |
RespondedByUserId | Guid | Да | Contact.RespondedByUserId | Immutable | Кто подтвердил (= AddresseeUserId, INV-CT-3). |
RespondedAt | DateTimeOffset | Да | Contact.RespondedAt | Immutable | Момент подтверждения (тайминг уведомления, авторитет — момент в messenger). |
Все поля иммутабельны: Accepted — терминальный факт подтверждения (последующий RemoveContact даёт
Cancelled и события подтверждения не отменяет; push удаления идёт через hub, не Kafka).
Когда публикуется
| Условие | Переход состояния | Согласованность |
|---|---|---|
Адресат подтвердил заявку AcceptContactRequest (актор = AddresseeUserId, INV-CT-3) | Pending → Accepted | связь Contact и факт подтверждения — в одном согласованном изменении |
Decline (Pending → Declined) и RemoveContact (Accepted → Cancelled) Kafka-события не
публикуют — только push в SignalR-hub. Создание заявки — контракт
contact-request-created.md.
Потребители (consumers)
| Consumer-контекст | Эффект (событие → что меняется) | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
notifications | Уведомление инициатору (RequesterUserId) о подтверждении (offline push/email; ФИО подтвердившего — из реплики identity consumer-а) | first-seen по MessageId (или ContactId) | < 1 мин |
Разблокировка звонков (INV-CT-5 над CallSession) и in-app push обеим сторонам — внутридоменные
эффекты messenger (не через это событие).
Гарантии доставки, порядок и обработка отказов
- Порядок per-
ContactId. КлючContactIdкладётcreatedиacceptedодной связи в один раздел —notificationsвидит их в порядке жизненного цикла (заявка → подтверждение). Между связями порядка нет. - At-least-once + идемпотентность. Доставка хотя бы раз;
notificationsдедуплицирует по идентификатору сообщения (MessageId, илиContactIdкак бизнес-ключ) — повторная доставка не шлёт второе уведомление инициатору. - Раскрытие профиля — не в событии. Полный профиль обеим сторонам открывает read-модель
messengerпо фактуAccepted(INV-CT-5), а не consumer по этому событию; событие несёт только id + момент, ФИО/телефон consumer резолвит из своей репликиidentity. - Retry/DLQ. Транзиентный сбой consumer-а — ретрай по offset; poison после исчерпания попыток → DLQ +
алерт, offset продвигается (уведомление может опоздать, факт подтверждения в
messengerуже необратим). - Терминальность.
Acceptedнеобратим: последующийRemoveContactдаётCancelledи события подтверждения не отменяет (push удаления — через hub, не Kafka), поэтому compensating-события у контракта нет.
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии входящего события с текущей consumer логирует warning и продолжает (апкаст схемы не выполняется). Топик кодирует версию суффиксом.v1по конвенции.- Additive-only для minor: новые поля — optional с безопасным default, без bump.
- Breaking — новый
ContractVersion = 2+ топикpin.messenger.contact-request-accepted.v2+ dual-publish + deadline миграции consumers + sunset.v1. - Порядок событий одной связи (
created→accepted) гарантирован ключомContactId; consumer держит дедуп поMessageId.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
ContactId, RequesterUserId, AddresseeUserId, RespondedByUserId, RespondedAt | Internal | include (корреляция сторон; не PII) |
| ФИО / телефон сторон | PII | exclude — резолв у consumer-а из реплики identity (профиль раскрывается обеим сторонам read-моделью messenger по факту Accepted) |
Событие несёт только id + момент. НЕ содержит ФИО/телефонов, IP/UA, secrets, tokens, платёжных данных.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (1): POST /api/messenger/contacts/requests/{contactId}/accept — Принять заявку в контакты
- События (2): События сервиса messenger: реестр топиков, Event Contract: pin.messenger.contact-request-created (ContactRequestCreatedEvent)
- registries (1): Реестр событий PIN
Связанные документы
- Источник (операция):
accept-contact-request(API-карта в README); сущность — contact.md (ContactRequestAccepted,ContactStatus, INV-CT-3/5); профиль — messenger-user.md, право звонка — call-session.md (INV-CALL-1). - Парный контракт: contact-request-created.md; реестр — события сервиса.
- ADR: 0055 мини-мессенджер, 0056 simplicity-first.