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 вообще
Теряем объяснимость и возможность реплея матчинга.