ADR-0003: Event Sourcing только для canonical-ядра

Status: accepted Date: 2026-04-17 Deciders: команда проекта

Контекст

Event Sourcing даёт выгоды аудита и восстановления, но несёт стоимость в сложности реализации. Нужно определить границу: что будет event-sourced, а что нет.

Решение

Event Sourcing применяется только к:

  • Canonical Product (характеристики, связи, статусы, replaced_by).
  • Manufacturer (+ aliases).
  • Identity Profile.
  • Matching Decision.
  • Equivalence Class.
  • Supplier Offer metadata (lifecycle, появление / исчезновение, см. секцию “Supplier Offer ES scope” ниже).
  • Estimate (версионирование для клиента).

Не применяется к:

  • Цены — current в PG, история в ClickHouse.
  • Остатки — то же.
  • Raw payload — immutable в S3.
  • Сессии, кэши.
  • Operational state (rate limiter, jobs).

Event store реализуется в PostgreSQL, распространение — через outbox → Kafka (см. ADR-0020).

Supplier Offer ES scope (точная граница)

Supplier Offer — “лёгкий” ES: в event_store пишутся только события lifecycle и метаданных канвы товара. Конкретно:

Тип событияПишется в event_storeПоля
OfferSeenдапервое наблюдение пары (supplier_ref, supplier_sku)
OfferLifecycleStatusChangedдаactive → discontinued, → archived
OfferCharacteristicsUpdatedдаdiff характеристик товарной канвы (что добавилось/изменилось/удалено)
OfferMediaUpdatedдаизменения media[]/certificates[]
MatchDecidedдаcanonical_product_ref, match_confidence, decided_by
OfferObservationRecorded (цена/остаток/условия)нет— append-only в supplier_offer_observation, история в ClickHouse
OfferPriceChanged / OfferStockChangedнетderived-события из observations, в Kafka, но не в event store

Правило для разработчика: если поле живёт в SupplierOffer (товарная канва), его изменение — событие в event_store. Если поле — атрибут SupplierOfferObservation (цена, остаток, условия) — пишется в обычную append-only таблицу и в offer.observation.v1 Kafka topic, БЕЗ event_store.

Последствия

Плюсы

  • Полный аудит всех значимых изменений каталога.
  • Возможность восстановить любое состояние во времени.
  • Проекции (ES, CH, read-models в PG) пересоздаваемы.
  • Границы ясны: объём event-sourced данных контролируемый.

Минусы

  • Нужно писать код upcasting’а при эволюции схем событий.
  • Переусложнение если event-sourcить всё подряд → поэтому граница.

Нейтральные последствия

  • Требуется дисциплина: все команды, меняющие canonical — через event store, без прямых UPDATE.

Рассмотренные альтернативы

Event sourcing всего

Избыточно. Цены/остатки меняются слишком часто, объём событий сделает систему тяжёлой.

Отказ от ES вообще

Теряем объяснимость и возможность реплея матчинга.

Ссылки