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 — в репо того сервиса.
Содержание
- Конвенции
- Envelope
- Versioning
- Retention
- DLQ naming
- Как добавлять новое событие
- Топики
- Что не в каталоге
Конвенции
Все события следуют единым правилам. Полный разбор 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>:
- Publisher начинает dual-publish — старый топик + per-entity, действие
проставлено в
Event-Type. TTL окна — 30 дней. - Consumer’ы переподписываются на per-entity-топик и маршрутизируют по
Event-Type(по одному PR на consumer). - Когда все consumer’ы переехали, publisher останавливает публикацию в старые топики; их consumer-group’ы удаляются.
- Старые топики удаляются из кластера через ещё 30 дней (retention).
Новые топики заводятся только в формате kazmaps.<service>.<entity>;
создать топик с <action> в имени — review-блокер.
Envelope
Каждое сообщение несёт metadata (Watermill message.Metadata). Это
cross-service контракт — consumer любого сервиса может полагаться на
эти поля.
| Поле | Что это |
|---|---|
Event-Type | Тип события (review.created). |
Schema-Version | Версия payload’а ("1"). |
Correlation-Id | ULID запроса, породившего событие. |
Source-Service | Имя publisher’а. |
Published-At | RFC3339Nano. |
traceparent | W3C trace context. |
Подробно — conventions/events.
Versioning
- Non-breaking (добавили опциональное поле) → оставь ту же
Schema-Version. Consumer’ы, ещё не знающие о новом поле, молча игнорируют его. - Breaking (удалили поле, сменили тип, переименовали) → два
варианта:
- Bump
Schema-Versionв metadata и временно dual-publish в старую и новую схему, пока consumer’ы не переехали. - Новый topic
kazmaps.<service>.<entity>.v2— для радикального редизайна payload’а. Старый топик живёт до остановки старых consumer’ов.
- Bump
История Schema-Version конкретного топика — в docs/events.md репо
publisher’а.
Retention
| Класс события | TTL в топике |
|---|---|
| Business event (regular) | 7 дней |
| Sensitive (ban, role change) | 30 дней |
| DLQ | 90 дней |
Retention настраивается на уровне Kafka cluster через topic config. Сервис не ретеншит сам.
DLQ naming
<topic>.dlqПример: kazmaps.review.review.dlq. Конфигурация Watermill
PoisonQueue — в conventions/events.
Как добавлять новое событие
- Придумай имя топика по конвенции выше.
- Открой
how-to/add-kafka-eventи пройди по шагам: payload type, publisher, outbox, envelope. - Добавь запись в таблицу ниже в том же PR, что и код события.
- Детальный payload опиши в
docs/events.mdрепо сервиса-publisher’а, в таблице ниже ссылайся наdocs/events.md#<topic>этого репо. - Если есть consumer(ы) — пропиши их тоже; список consumer’ов обновляет тот, кто подписывается, в том же PR, которым заводит handler.
Топики
Детальный payload и Schema-Version history — в репо publisher’а. Таблица ниже — directory: topic | publisher | consumers | payload source.
user (publisher: user)
| Topic | Publisher | Consumers | Payload source |
|---|---|---|---|
kazmaps.user.account | user | notification | docs/events.md в репо user-service — Event-Type: account.registered/updated/banned/unbanned/level_changed |
kazmaps.user.push_token | user | notification | docs/events.md в репо user-service — Event-Type: push_token.created/updated/deleted |
review (publisher: review)
| Topic | Publisher | Consumers | Payload source |
|---|---|---|---|
kazmaps.review.review | review | notification | docs/events.md в репо review-service — Event-Type: review.created/updated/deleted/replied |
media (publisher: media)
| Topic | Publisher | Consumers | Payload source |
|---|---|---|---|
kazmaps.media.photo | media | catalog | docs/events.md в репо media-service — Event-Type: photo.uploaded/deleted/tagged |
kazmaps.media.internal.processing | media | media (internal pipeline) | docs/events.md в репо media-service |
Топик kazmaps.media.internal.processing — внутренний pipeline-топик сервиса
media: handler загрузки кладёт задачу processing’а, worker того же
сервиса её забирает. Это не межсервисное API, другие сервисы не
подписываются.
catalog (publisher: catalog)
| Topic | Publisher | Consumers | Payload source |
|---|---|---|---|
kazmaps.catalog.place | catalog | listings, indoor, notification | docs/events.md в репо catalog — Event-Type: place.created/updated/closed/verified/category_changed/needs_canonical/rating_updated |
kazmaps.catalog.correction | catalog | notification | docs/events.md в репо catalog — Event-Type: correction.approved/rejected |
Consume-side DLQ — один консолидированный kazmaps.catalog.dlq (исходный топик в metadata).
listings (publisher: listings)
| Topic | Publisher | Consumers | Payload source |
|---|---|---|---|
kazmaps.listings.provider | listings | catalog | docs/events.md в репо listings — Event-Type: provider.created/updated/deleted |
kazmaps.listings.service | listings | catalog | docs/events.md в репо listings — Event-Type: service.created/updated/deleted |
kazmaps.listings.product | listings | catalog | docs/events.md в репо listings — Event-Type: product.created |
kazmaps.listings.charging_station | listings | — | docs/events.md в репо listings — Event-Type: charging_station.created |
kazmaps.listings.provider_service | listings | — | docs/events.md в репо listings — Event-Type: provider_service.linked/unlinked |
indoor (publisher: indoor)
Топик на сущность: kazmaps.indoor.<entity>, Event-Type <entity>.created/updated/deleted.
| Topic | Publisher | Consumers | Payload source |
|---|---|---|---|
kazmaps.indoor.building | indoor | — | docs/events.md в репо indoor |
kazmaps.indoor.floor_plan | indoor | — | 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.catalog.place (Event-Type place.closed), kazmaps.geo.building (Event-Type building.deleted).
moderation (publisher: внешний модерационный сервис)
Publisher живёт вне списка handbook-овских сервисов; consumer’ы потребляют по факту появления сообщений в топике.
| Topic | Publisher | Consumers | Payload source |
|---|---|---|---|
kazmaps.moderation.photo | внешний модерационный сервис | media | docs/events.md в репо media-service (consumed-contract) — Event-Type: photo.approved/rejected |
kazmaps.moderation.item | внешний модерационный сервис | notification | docs/events.md в репо notification-service (consumed-contract) — Event-Type: item.rejected |
Для consumed-only контрактов ссылка указывает на репо одного из consumer’ов, где контракт зафиксирован со стороны consumer’а; при появлении publisher-репо эти записи переедут туда.
DLQ-топики
Все основные топики имеют пару <topic>.dlq. Сообщения попадают туда
после исчерпания retry. Чтение DLQ — ручное, через psql /
kafka-console-consumer, по инциденту. Retention 90 дней.
Примеры:
kazmaps.review.review.dlq
kazmaps.media.photo.dlq
kazmaps.catalog.dlq # consume-side, консолидированный
kazmaps.indoor.building.dlqЧто не в каталоге
В этот каталог не входят:
- Тестовые топики (
*_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.