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

Event Contract: pin.investor-owner.property-added (InvestorPropertyAddedKafkaEvent)

Версионируемый контракт интеграционного события «объект появился в портфеле инвестора». Consumer-документы ссылаются сюда, не переописывая payload. Дом полей агрегата — investor-property.md (раздел «Доменные и интеграционные события»).

Назначение

Фиксирует создание записи InvestorPropertyEntity — купленный объект недвижимости появился в портфеле инвестора («Мои объекты недвижимости», /profile/my-lots), независимо от источника: завершение сделки в CRM или синхронизация владений из Talent Owners. Публичное: даёт downstream-контекстам факт появления объекта без синхронного запроса в investor-ownernotifications отправляет уведомление инвестору, user-activity пишет запись в ленту событий пользователя (/profile/events).

Metadata

ПолеЗначение
Event typepin.investor-owner.property-added
Имя событияInvestorPropertyAddedKafkaEvent
Topicpin.investor-owner.property-added.v1
Partitions / key3 партиции (единицы-десятки объектов на инвестора, низкий поток, ADR-0056) / key = Id записи портфеля
ContractVersion1
Producerinvestor-owner: consumers crm-deal-completed, talent-owners-sync; admin-операция create-investor-property (компенсирующее исправление)
Статусdraft

Событие несёт метаданные конверта: MessageId (Guid v7), CreatedAt, версия контракта, AggregateId = Id, AggregateType = "InvestorProperty". Публикуется через транзакционный outbox атомарно с созданием записи.

Payload InvestorPropertyAddedKafkaEvent

Дом полей — entity: InvestorPropertyEntity; здесь перечислено только то, что реально в событии.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
IdGuidДаInvestorPropertyEntity.IdImmutableКлюч записи (= Kafka key).
InvestorUserIdGuidДаInvestorPropertyEntity.InvestorUserIdImmutableИнвестор-владелец (identity.users.id); адресат уведомления и ленты.
PropertyObjectIdGuidДаInvestorPropertyEntity.PropertyObjectIdImmutableОбъект недвижимости (chessboard); при Source = TalentOwnersSync без сопоставления с шахматкой — стабильный id объекта Talent (правило PropertyObjectId consumer-а синхронизации).
ProjectIdGuid?НетInvestorPropertyEntity.ProjectIdAdditive onlyПроект (ЖК/отель); null, если объект не сопоставлен с шахматкой PIN.
Sourcestring (enum PropertySource)ДаInvestorPropertyEntity.SourceAdditive only (новые значения)PinCrmDeal / TalentOwnersSync / AdminCorrection. Позволяет consumer-ам различать «поздравляем с покупкой» (сделка PIN) и «импорт существующего владения» (синхронизация).
PurchasePriceMoneySnapshotДаInvestorPropertyEntity.PurchasePriceAdditive onlyЗа сколько купили (итоговая цена сделки / цена из Talent Owners).
CurrencyCurrencySnapshotДасправочник валют handbook на момент публикацииAdditive onlyDisplay-снапшот валюты PurchasePrice.CurrencyId — поля модели см. snapshots.md (не переописываются).
PurchasedAtDateTimeOffsetДаInvestorPropertyEntity.PurchasedAtAdditive onlyДата покупки (завершение сделки).
VersionlongДаInvestorPropertyEntity.VersionImmutable semanticsДоменная версия; Version = 1 ⇔ создание записи.

Вложенная модель MoneySnapshot

ПолеТипОбязательностьОграниченияОписание
AmountdecimalДа> 0 (цена покупки, INV-4 дома полей)Сумма.
CurrencyIdshortДассылка на справочник валют handbook (RUB-only, ADR-0048)Валюта — типизированная ссылка; display-код — только внутри CurrencySnapshot события.

Событие с денежной суммой несёт CurrencyId + CurrencySnapshot (правило snapshot-моделей handbook — snapshots.md); снапшот фиксируется на момент публикации и не «догоняется» при последующих изменениях справочника.

Минимальность payload (обоснование, ADR-0056)

Контракт сознательно минимален — только идентификаторы и факт покупки:

  • PropertyObjectSnapshot (наименование/номер/площадь/этаж) в payload НЕ дублируется: notifications формирует текст по шаблону с deeplink на /profile/my-lots (данные объекта недвижимости фронт рендерит из catalog по PropertyObjectId), user-activity хранит в ленте только ссылочную запись PropertyObjectId. Дублирование снапшота в событии создало бы вторую копию display-данных с собственным жизненным циклом (снапшот живёт в агрегате, актуальные данные — в catalog/chessboard).
  • PII отсутствует: телефон/ФИО собственника не передаются (правило дома owner-mapping.md); контакты для доставки уведомления notifications берёт из собственных данных получателя по InvestorUserId.
  • Финансовые поля ограничены покупкой: SalePrice/RealizedProfit не входят — продажа публикуется отдельным событием pin.investor-owner.property-sold.v1, смешивание семантик в одном контракте потребовало бы optional-полей со сложными правилами заполнения.
  • Расширение — additive-only: если потребителю понадобится, например, BuildingId или DealId, поле добавляется как optional без bump ContractVersion — стартовать с широкого контракта «на вырост» запрещено ADR-0056.

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

УсловиеПереход состоянияСогласованность
Consumer crm-deal-completed: создание записи после pin.crm.deal-completed.v1[*] → Activeзапись и событие — в одном согласованном изменении
Consumer talent-owners-sync: создание записи из синхронизации Talent Owners[*] → Active; при владении, пришедшем уже проданным, — [*] → Sold (тогда атомарно публикуется и property-sold)та же
Admin-операция create-investor-property (компенсирующее исправление Службой заботы, permission investor-owner.portfolio.manage)[*] → Activeта же

Upsert существующей записи синхронизацией (INV-7) событие НЕ публикует — оно фиксирует именно появление объекта в портфеле; переход Active → Sold — отдельное событие property-sold.

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

Consumer-контекстConsumerЭффектИдемпотентностьЗадержка (SLA)
notificationsconsumer investor-property-addedУведомление инвестору (push/СМС по правилам каналов notifications): при Source = PinCrmDeal — «объект появился в вашем портфеле» с deeplink /profile/my-lots; при Source = TalentOwnersSync — сдержанный шаблон импорта (или подавление при массовом первичном импорте)MessageId< 5 мин
user-activityconsumer investor-property-addedЗапись ленты событий пользователя (/profile/events): тип «покупка объекта», PropertyObjectId, PurchasedAtMessageId + business key Id< 5 мин

Порядок доставки и согласованность

  • Доставка — at-least-once: notifications и user-activity дедуплицируют по MessageId (плюс business key Id у user-activity); повтор доставки не создаёт дублей уведомления/записи ленты.
  • Промежуточное состояние для инвестора: запись портфеля видна в «Мои объекты недвижимости» сразу после коммита агрегата (страница читает агрегат напрямую), а это событие доставляется downstream асинхронно (SLA < 5 мин) — инвестор может увидеть объект в списке раньше, чем получит уведомление о нём. Между топиками (owner-linked, property-sold) порядок не гарантирован (см. «Версионирование»).

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

  • Версия — один ContractVersion = 1; consumer при несовпадении версии события логирует warning и продолжает (warn-and-proceed). Суффикс .v1 в имени топика — конвенция.
  • Additive-only для minor: новые поля — optional с безопасным default, без bump.
  • Breaking (удаление/переименование/смена типа или смысла) — ContractVersion = 2 + новый топик pin.investor-owner.property-added.v2 + dual-publish на период миграции + deadline миграции consumers + sunset .v1.
  • Unknown enum Source — fallback: сохранить строку как есть + warning; notifications при неизвестном источнике использует нейтральный шаблон (fail-safe, уведомление не теряется).
  • Порядок per-property гарантирован key = Id (события property-addedproperty-sold одного объекта публикуются в разные топики; consumer-ы property-sold не должны требовать предварительной обработки property-added — идемпотентный upsert по Id).

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

ПолеКласс данныхПравило
Id, PropertyObjectId, ProjectId, Source, PurchasedAt, Version, Currency (снапшот валюты)Internalinclude
InvestorUserIdInternal (ссылка на пользователя, не PII)include
PurchasePrice.*Sensitive (финансовые данные инвестора)include только для доверенных consumer-ов — доступ к топику ограничен m2m ACL Kafka (notifications, user-activity); в логах producer/consumer суммы не пишутся открытым текстом

Событие НЕ содержит secrets, tokens, телефонов/ФИО (PII собственника остаётся в investor-owner), платёжных реквизитов и unbounded-вложений.

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

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

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