Skip to Content
Каталог событий

Event catalog

Каталог Kafka-топиков. Одна запись на топик: как называется, кто publisher, кто consumer’ы, где лежит payload.

Эта страница — directory. Cross-service контракт — это envelope (поля metadata) и правила naming / versioning. Детальный payload каждого топика и история Schema-Version живут в репо publisher’а (docs/events.md — см. conventions/service-readme).

Связка с services-catalog: сервис в каталоге перечисляет префиксы своих топиков; payload — в репо того сервиса.

Содержание

Конвенции

Все события следуют единым правилам. Полный разбор Watermill / outbox — в conventions/events.

Topic naming

kazmaps.<service>.<entity>
  • <service> — имя сервиса-publisher’а (user, review, media, …).
  • <entity> — доменная сущность в единственном числе (place, photo, account, building).
  • Действие (created, updated, deleted, banned, …) — НЕ в имени топика, а в заголовке Event-Type (см. Envelope).

Топик — это поток событий одной сущности, а не одного действия. Все изменения сущности (created → updated → closed) идут в один топик. Ключ записи = id сущности.

Канон — только kazmaps.<service>.<entity>. Никаких коротких имён без префикса и без <action> в имени топика.

Почему сущность, а не событие

Ключ записи = id сущности, поэтому все события одной сущности попадают в одну партицию и обрабатываются строго по порядку. Если разнести действия по разным топикам (...place.created, ...place.updated), межтопиковый порядок Kafka не гарантирует — денормализатор может применить updated раньше created. Per-entity-топик с ключом-сущностью это исключает — это требование корректности для stateful-consumer’ов (денормализация, инвалидация кэша, проекции), а их в платформе большинство.

Цена — нет подписки на отдельное действие на уровне топика; она почти не нужна и заменяется фильтром по Event-Type в consumer’е (диспетчер и так маршрутизирует по Event-Type).

Миграция legacy

Короткие имена (review.created, photo.uploaded, user.banned) и ранее-канонические per-action имена (kazmaps.<service>.<entity>.<action>) мигрируют на per-entity kazmaps.<service>.<entity>:

  1. Publisher начинает dual-publish — старый топик + per-entity, действие проставлено в Event-Type. TTL окна — 30 дней.
  2. Consumer’ы переподписываются на per-entity-топик и маршрутизируют по Event-Type (по одному PR на consumer).
  3. Когда все consumer’ы переехали, publisher останавливает публикацию в старые топики; их consumer-group’ы удаляются.
  4. Старые топики удаляются из кластера через ещё 30 дней (retention).

Новые топики заводятся только в формате kazmaps.<service>.<entity>; создать топик с <action> в имени — review-блокер.

Envelope

Каждое сообщение несёт metadata (Watermill message.Metadata). Это cross-service контракт — consumer любого сервиса может полагаться на эти поля.

ПолеЧто это
Event-TypeТип события (review.created).
Schema-VersionВерсия payload’а ("1").
Correlation-IdULID запроса, породившего событие.
Source-ServiceИмя publisher’а.
Published-AtRFC3339Nano.
traceparentW3C trace context.

Подробно — conventions/events.

Versioning

  • Non-breaking (добавили опциональное поле) → оставь ту же Schema-Version. Consumer’ы, ещё не знающие о новом поле, молча игнорируют его.
  • Breaking (удалили поле, сменили тип, переименовали) → два варианта:
    1. Bump Schema-Version в metadata и временно dual-publish в старую и новую схему, пока consumer’ы не переехали.
    2. Новый topic kazmaps.<service>.<entity>.v2 — для радикального редизайна payload’а. Старый топик живёт до остановки старых consumer’ов.

История Schema-Version конкретного топика — в docs/events.md репо publisher’а.

Retention

Класс событияTTL в топике
Business event (regular)7 дней
Sensitive (ban, role change)30 дней
DLQ90 дней

Retention настраивается на уровне Kafka cluster через topic config. Сервис не ретеншит сам.

DLQ naming

<topic>.dlq

Пример: kazmaps.review.review.dlq. Конфигурация Watermill PoisonQueue — в conventions/events.

Как добавлять новое событие

  1. Придумай имя топика по конвенции выше.
  2. Открой how-to/add-kafka-event и пройди по шагам: payload type, publisher, outbox, envelope.
  3. Добавь запись в таблицу ниже в том же PR, что и код события.
  4. Детальный payload опиши в docs/events.md репо сервиса-publisher’а, в таблице ниже ссылайся на docs/events.md#<topic> этого репо.
  5. Если есть consumer(ы) — пропиши их тоже; список consumer’ов обновляет тот, кто подписывается, в том же PR, которым заводит handler.

Топики

Детальный payload и Schema-Version history — в репо publisher’а. Таблица ниже — directory: topic | publisher | consumers | payload source.

user (publisher: user)

TopicPublisherConsumersPayload source
kazmaps.user.accountusernotificationdocs/events.md в репо user-service — Event-Type: account.registered/updated/banned/unbanned/level_changed
kazmaps.user.push_tokenusernotificationdocs/events.md в репо user-service — Event-Type: push_token.created/updated/deleted

review (publisher: review)

TopicPublisherConsumersPayload source
kazmaps.review.reviewreviewnotification, listings, searchdocs/architecture.md («События») в репо review — Event-Type: review.created/updated/deleted/hidden/restored/replied/liked (тонкий жизненный цикл, только идентификаторы) и review.subject_stats_changed (снимок агрегата {subject_type, subject_id, rating_avg, review_count} — единственный носитель оценки; listings и search читают только его). Сидирование нового потребителя — POST /internal/reviews/stats/republish

media (publisher: media)

TopicPublisherConsumersPayload source
kazmaps.media.photomedia—docs/events.md в репо media-service — Event-Type: photo.uploaded/deleted/tagged/hidden/restored
kazmaps.media.internal.processingmediamedia (internal pipeline)docs/events.md в репо media-service

Топик kazmaps.media.internal.processing — внутренний pipeline-топик сервиса media: handler загрузки кладёт задачу processing’а, worker того же сервиса её забирает. Это не межсервисное API, другие сервисы не подписываются.

listings (publisher: listings)

TopicPublisherConsumersPayload source
kazmaps.listings.providerlistings—docs/events.md в репо listings — Event-Type: provider.created/updated/deleted
kazmaps.listings.servicelistings—docs/events.md в репо listings — Event-Type: service.created/updated/deleted
kazmaps.listings.productlistings—docs/events.md в репо listings — Event-Type: product.created
kazmaps.listings.charging_stationlistings—docs/events.md в репо listings — Event-Type: charging_station.created
kazmaps.listings.provider_servicelistings—docs/events.md в репо listings — Event-Type: provider_service.linked/unlinked

indoor (publisher: indoor)

Топик на сущность: kazmaps.indoor.<entity>, Event-Type <entity>.created/updated/deleted.

TopicPublisherConsumersPayload source
kazmaps.indoor.buildingindoor—docs/events.md в репо indoor
kazmaps.indoor.floor_planindoor—docs/events.md в репо indoor
kazmaps.indoor.{wall,wall_adjacency,space,area,fixture,marker,vertical_connector,vc_endpoint}indoor—docs/events.md в репо indoor

indoor consume: kazmaps.geo.building (Event-Type building.deleted).

editorial (publisher: editorial)

Envelope и payload — docs/contracts.md §2–§3 в репо editorial. Все пять топиков пишет один форвардер аутбокса: топик, не заведённый на брокере, останавливает форвардер целиком (пауза, не вердикт), поэтому заводятся все разом.

TopicPublisherConsumersPayload source
kazmaps.editorial.editeditorialfeature-api, searchEvent-Type: edit.applied (снимок head, event_version 2), edit.reverted
kazmaps.editorial.releaseeditorialfeature-api, searchEvent-Type: release.imported — порог гашения оверлея overlay_quenched_through
kazmaps.editorial.proposaleditorial—Event-Type: proposal.submitted/approved/rejected/returned/withdrawn/superseded/unapproved
kazmaps.editorial.overrideeditorial—Event-Type: override.set/lifted
kazmaps.editorial.reporteditorial—Event-Type: report.created/resolved

ownership (publisher: ownership)

TopicPublisherConsumersPayload source
kazmaps.ownership.claimownershipeditorialEvent-Type: claim.submitted/verified/rejected/withdrawn/revoked, membership.granted/revoked — internal/event в репо ownership

canon (publisher: feature-api)

Топик назван по сущности канона, а не по сервису: публикует его тот, кто первым читает релиз, и это право может переехать вместе с ролью. ADR — deploy-data/docs/canon-lifecycle-topic.md.

TopicPublisherConsumersPayload source
kazmaps.canon.entityfeature-apimedia, review, listingsEvent-Type: entity.redirected/retired/restored/closed/reopened — docs/architecture.md в репо feature-api

DLQ-топики

Все основные топики имеют пару <topic>.dlq. Сообщения попадают туда после исчерпания retry. Чтение DLQ — ручное, через psql / kafka-console-consumer, по инциденту. Retention 90 дней.

Примеры:

kazmaps.review.review.dlq kazmaps.media.photo.dlq kazmaps.listings.dlq # consume-side, консолидированный kazmaps.indoor.building.dlq

Что не в каталоге

В этот каталог не входят:

  • Выведенные домены kazmaps.catalog.* и kazmaps.moderation.*. Оба сервиса выведены из эксплуатации (решение ADR v1.3 §0, чек-лист — kazmaps-main/editorial#12), репозитории архивированы. Продюсеров у этих топиков больше нет, консьюмеры сняты, из kafka_managed_topics стенда топики убраны. Записи удалены отсюда по правилу «сервис retired → удали запись»; строка здесь остаётся, чтобы чтение старого кода не выглядело находкой.
  • Тестовые топики (*_test, *-local) — локальная разработка.
  • Топики внешних доменов, на которые notification уже подписан, но чьи publisher-сервисы ещё не переведены на канон / не существуют: kazmaps.booking.booking, kazmaps.queue.ticket, kazmaps.geo.correction, kazmaps.nav.correction, kazmaps.achievement.achievement, kazmaps.message.message, kazmaps.favorite.favorite, kazmaps.event.event, kazmaps.coupon.coupon, kazmaps.safety_report.report. Они попадут в каталог, когда соответствующие сервисы начнут публиковать реальные события; до этого notification просто подписан и ничего не получает.
  • Internal команды через HTTP — это REST, см. conventions/http-api.
Last updated on