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

Event Contract: pin.crm.booking-confirmed (BookingConfirmedEvent)

Версионируемый контракт интеграционного события «бронь подтверждена». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей — entity: BookingEntity (раздел «Доменные и интеграционные события», строка BookingConfirmedEvent); здесь перечислено только то, что реально в событии.

Назначение

Фиксирует переход брони в состояние Booked — объект окончательно закреплён за сделкой по программе бронирования застройщика (booking-program.md). Публичное: даёт downstream snapshot без синхронного запроса в crm, чтобы chessboard перевёл объект недвижимости в статус «Бронь» и завёл таймер брони (BookingExpiresAt), не читая таблицы CRM (границы контекстов — service-boundaries.md). Смену статуса объекта в шахматке выполняет консюмер chessboard; CRM чужие таблицы не пишет.

Metadata

ПолеЗначение
Event typepin.crm.booking-confirmed
Topicpin.crm.booking-confirmed.v1
Partitions / key6 (ADR-0056, 1–10k активных) / key = AggregateId = BookingId — per-booking порядок
ContractVersion1
Producercrm: подтверждение брони (переход → Booked, booking.md INV-BKG-3/INV-BKG-4)
Статусdraft

Событие публикуется атомарно с мутацией агрегата: пока переход не зафиксирован, событие не отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = BookingId.

Payload BookingConfirmedEvent

Строго типизированный snapshot момента подтверждения. Имена — реальные поля BookingEntity (booking.md); в скобках — семантический ярлык постановки. BookingExpiresAt и BookedAt в агрегате nullable (DateTimeOffset?), но на переходе → Booked оба заполнены, поэтому в payload обязательны.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
BookingIdGuidДаBookingEntity.IdImmutableКлюч агрегата (= AggregateId, Kafka key).
DealIdGuidДаBookingEntity.DealIdImmutableСделка-владелец процесса (deal.md).
TenantIdGuidДаBookingEntity.TenantIdImmutableЗастройщик объекта (DeveloperCompanyId), tenant boundary.
PropertyObjectIdGuidДаBookingEntity.PropertyObjectIdImmutableОбъект шахматки; chessboard ставит статус «Бронь».
ProgramIdGuidДаBookingEntity.ProgramIdImmutableПрограмма бронирования (BookingProgramId); условия — иммутабельный snapshot в брони.
BookingExpiresAtDateTimeOffsetДаBookingEntity.BookingExpiresAtAdditive onlyДедлайн фазы «Бронь» = BookedAt + ProgramTerms.BookingDurationHours (таймер «BookingUntil»); chessboard заводит таймер до этого момента.
BookedAtDateTimeOffsetДаBookingEntity.BookedAtImmutableМомент подтверждения брони (→ Booked, «ConfirmedAt»).
Что не входит в payload

Payload НЕ несёт условий программы (уже разосланы snapshot-ом в BookingCreatedEvent), сумм/оплаты (см. BookingPaidEvent), реквизитов (RequisiteSetId — только логическая ссылка на profile) и ФИО/телефонов клиента.

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

УсловиеПереход состоянияПубликация
Бесплатная программа (ProgramTerms.IsPaid = false): подтверждение брони до ReserveExpiresAt (INV-BKG-3)Reserved → Bookedатомарно со сменой состояния агрегата
Платная программа: подтверждение оплаты по чеку менеджером застройщика + подтверждение брони при PaymentStatus = Paid (INV-BKG-3/4)AwaitingPayment → Bookedта же

На переходе фиксируются BookedAt = now и BookingExpiresAt = BookedAt + ProgramTerms.BookingDurationHours, а также доменное событие BookingConfirmed для таймлайна сделки; смена состояния и публикация интеграционного BookingConfirmedEvent происходят в одном согласованном изменении.

Гонка условий

Гонка «истечение по таймеру / подтверждение менеджером» разрешается оптимистичной блокировкой по версии — проигравший повторяет операцию с перечиткой, поэтому событие публикуется ровно для победившего перехода.

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

Сверено с booking.md (BookingConfirmedEvent, консюмеры). Головной потребитель — chessboard.

Consumer-контекстSide effectsИдемпотентностьЗадержка (SLA)
chessboardОбъект недвижимости PropertyObjectId → статус «Бронь» + таймер до BookingExpiresAt (проекция)MessageId + first-seen BookingId< 1 мин
catalogОбновление статуса объекта недвижимости в витрине (проекция шахматки)MessageId< 5 мин
notificationsУведомление участникам сделки «бронь подтверждена»MessageId< 5 мин

Все консюмеры идемпотентны по MessageId (повторная доставка Kafka не даёт двойного эффекта); статус объекта недвижимости — идемпотентный upsert проекции.

Версионирование и совместимость

  • ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик кодирует версию суффиксом .v1 по конвенции.
  • Additive-only для minor: новые поля — как optional с безопасным default, без bump.
  • Breaking (удаление/переименование/смена типа/смысла) — новый ContractVersion = 2 + топик pin.crm.booking-confirmed.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Enum в payload нет (только Guid/DateTimeOffset) — unknown-enum правило неприменимо.
  • Порядок per-booking гарантирован key-ом (BookingId); лайфцикл объекта недвижимости между разными топиками брони (booking-created/-confirmed/-expired/-cancelled) консюмер упорядочивает по бизнес-состоянию, а не по кросс-топик offset.

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

ПолеКласс данныхПравило
BookingId, DealId, TenantId, PropertyObjectId, ProgramIdInternalinclude
BookingExpiresAt, BookedAtInternalinclude
Без PII

Событие содержит только идентификаторы и метки времени — PII (ФИО/телефоны клиента) в payload отсутствует, поэтому маскирование полей не требуется; ФИО/телефон живут в identity/profile и в логах producer/consumer маскируются по общему правилу (+7***1234, инициалы). Событие НЕ содержит secrets, tokens, credentials, платёжных реквизитов, эскроу-счетов и unbounded вложений. Tenant boundary передан явным TenantId; доступ к топику ограничен m2m ACL Kafka (chessboard/catalog/notifications).

Доставка, retry/DLQ и восстановление

  • Гарантия. At-least-once после атомарной публикации; ключ = BookingId — per-booking порядок сохранён. Дубликаты возможны — консюмеры идемпотентны.
  • Идемпотентность. chessboardMessageId + first-seen BookingId (статус «Бронь» — идемпотентный upsert проекции); catalog/notifications — по MessageId.
  • Retry. Транзиентный сбой консюмера → повтор с backoff без продвижения оффсета этой брони; «уже применено» (объект недвижимости уже «Бронь») — no-op, не ошибка.
  • DLQ. Неразбираемое/несовместимое сообщение после конечного числа попыток → DLQ + алерт; другие брони не блокируются.
  • Кросс-топик порядок. booking-confirmed может обогнать или отстать от других фаз брони в своих топиках; chessboard упорядочивает лайфцикл объекта недвижимости по бизнес-состоянию: если бронь уже освобождена/отменена (обработаны -expired/-cancelled) — «Бронь» не выставляется (fail-safe). Отсутствие ранее виденного booking-created для этого объекта недвижимости не блокирует обработку — целевой статус «Бронь» самодостаточен (snapshot условий не нужен, он был в booking-created).
  • Промежуточное состояние. Таймлайн-строка BookingConfirmed и BookingExpiresAt видны в карточке сделки сразу (данные crm); смена цвета объекта недвижимости в шахматке и уведомление — с задержкой (< 1–5 мин).
  • Гонка «истечение ↔ подтверждение». Разрешена на источнике оптимистической блокировкой (проигравший перечитывает состояние), поэтому BookingConfirmedEvent публикуется ровно для победившего перехода — консюмер не получает противоречивую пару «confirmed для уже истёкшей брони».

Acceptance-критерии (контракт потребления)

  • chessboard — бронь. Дано: опубликован BookingConfirmedEvent. Когда chessboard обрабатывает. Тогда объект недвижимости → «Бронь» + таймер до BookingExpiresAt.
  • Идемпотентность. Дано: событие доставлено дважды. Когда обрабатывается повторно. Тогда статус объекта недвижимости не меняется повторно.
  • Устаревшее событие. Дано: для этой брони уже обработана отмена/истечение. Когда приходит запоздавший booking-confirmed. Тогда «Бронь» не ставится (fail-safe).
  • catalog/notifications. Дано: событие опубликовано. Когда обрабатывают консюмеры. Тогда витрина обновляет статус объекта недвижимости, участники сделки получают уведомление «бронь подтверждена».
  • Несовместимый payload. Дано: сообщение без обязательного поля. Когда повтор не помог. Тогда DLQ + алерт.

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

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

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