Событийная архитектура
Событийная архитектура (EDA)
1) Что такое событие и зачем EDA
Событие — неизменяемый факт, уже произошедший в домене (“PlayerVerified”, “PaymentCaptured”). EDA строит интеграции вокруг публикации этих фактов и реакций на них:- слабая связность сервисов,
- масштабирование потребителей независимо,
- реплей/перестройка проекций,
- прозрачный аудит.
EDA не отменяет синхронные API — она дополняет их, вынося кросс-сервисные зависимости в асинхронный слой.
2) Типы событий
Доменные: значимые бизнес-факты (OrderPlaced, BonusGranted).
Интеграционные: “снимки”/изменения для внешних систем (UserUpdated, WalletBalanceChanged).
Технические: жизненный цикл и телеметрия (Heartbeat, PipelineFailed).
Команды (не события, но рядом): инструкции “сделай X” (CapturePayment).
Рекомендация: доменные события — первичны; интеграционные формируются проекциями для конкретных потребителей.
3) Контракты событий и схемы
Схема: Avro/Protobuf/JSON Schema + Schema Registry; стратегия совместимости: `BACKWARD` для эволюции потребителей, `FULL` на критичных темах.
CloudEvents (id, source, type, time, subject, datacontenttype) — единообразные заголовки.
Обязательные метаданные: `event_id` (ULID/UUID), `occurred_at`, `producer`, `schema_version`, `correlation_id`/`causation_id`, `idempotency_key`.
Версионирование: add-only поля, запрет переименований/семантических ломаний; новые типы — новые темы/типы.
json
{
"type":"record","name":"PaymentCaptured","namespace":"events.v1",
"fields":[
{"name":"event_id","type":"string"},
{"name":"occurred_at","type":{"type":"long","logicalType":"timestamp-micros"}},
{"name":"payment_id","type":"string"},
{"name":"amount","type":{"type":"bytes","logicalType":"decimal","precision":18,"scale":2}},
{"name":"currency","type":"string"},
{"name":"player_id","type":"string"}
]
}
4) Доставка, порядок и согласованность
At-least-once как дефолт → необходима идемпотентность обработчиков.
Порядок: гарантируется внутри партиции (Kafka) или очереди (RabbitMQ), но может нарушаться при ретраях; ключ события должен отражать доменную гранулу порядка (например, `player_id`).
Согласованность: для денег/кредитов — только через журналы/саги/компенсации; избегайте LWW.
Модель чтения: проекции и кэши могут быть eventual — показывайте «идет обновление…» и используйте RNOT-стратегии для строгих путей.
5) Outbox/Inbox и CDC
Outbox: сервис пишет факт в свою БД и в таблицу outbox в одной транзакции → воркер публикует в шину.
Inbox: потребитель сохраняет `event_id` с результатом обработки для дедупа.
CDC (Change Data Capture): поток изменений из БД (binlog/WAL) в шину для построения интеграций без изменений приложения.
Idempotency: обработка по `idempotency_key`/`event_id`, не менять внешний мир до фиксации.
6) CQRS и Event Sourcing
CQRS: разделяем write-модель и read-проекции; проекции строятся из событий и могут отставать.
Event Sourcing: состояние агрегата = свертка его событий. Плюсы: полный аудит/реплей; минусы: сложность миграций/схем/снапшоты.
Практика: ES — не везде, а там, где важна история и компенсации; CQRS — почти всегда в EDA.
7) Саги: оркестрация и хореография
Оркестрация: координатор посылает команды и ждет событий-ответов; удобна для сложных процессов (KYC→Deposit→Bonus).
Хореография: сервисы реагируют на события друг друга; проще, но сложнее прослеживать.
Всегда определяйте компенсации и дедлайны шагов.
8) Проектирование топологий (Kafka/RabbitMQ)
Kafka
Топик per событие домена: `payments.captured.v1`, `players.verified.v1`.
Ключ партиционирования: `player_id`/`wallet_id` — там, где важен порядок.
`replication.factor=3`, `min.insync.replicas=2`, продьюсер `acks=all`.
Retention: по времени (напр. 7–90 дней) и/или compaction (последнее состояние по ключу).
Топики для retry и DLQ с backoff.
RabbitMQ
Exchanges: `topic`/`direct`, routing key `payments.captured.v1`.
Для широкого фан-аута — `topic`+несколько очередей; для RPC/команд — отдельные очереди.
Quorum Queues для HA; TTL + dead-letter exchange для ретраев.
9) Наблюдаемость и SLO EDA
SLI/SLO:- End-to-end latency (occurred_at → обработано): p50/p95/p99.
- Lag/age: отставание потребителей (Kafka consumer lag, Rabbit backlog age).
- Throughput публикации/обработки.
- DLQ-rate и доля повторов.
- Успех бизнес-операций (напр., «депозит подтвержден ≤ 5с»).
- Корреляция событий через `trace_id`/`correlation_id` (OTel).
- Экземпляры (exemplars) из метрик → трассы.
- Дашборды “Producer→Broker→Consumer” с burn-rate алертами.
10) Реплей, ретеншн и backfill
Реплей для перестроения проекций/исправления багов: гоняйте в новую проекцию/неймспейс, затем переключайте чтение.
Ретеншн: юридические/бизнес-требования (GDPR/PCI); чувствительные поля — шифруйте и/или токенизируйте.
Backfill: одноразовые темы/очереди, четкие лимиты RPS, чтобы не задушить прод.
11) Безопасность и комплаенс
TLS in-transit, mTLS для внутренних клиентов.
Авторизация: per-topic/per-exchange ACL; multitenancy через namespace/vhost.
PII: минимизировать поля в событии; envelope метаданные отдельно, полезные нагрузки шифровать при необходимости.
Аудит доступа к событиям, запрет «все-могущих» ключей.
Политики ретеншна и право на удаление (GDPR): либо храните ссылки на данные, либо tombstone-события и удаление в проекциях.
12) Тестирование в EDA
Contract tests: потребители валидируют свои ожидания схем (consumer-driven).
Replay-тесты: прогон исторической выборки через новый обработчик/версию схемы.
Chaos-сценарии: задержка/потери брокера, падение узлов, отставание потребителя → SLO остаются в рамках.
Smoke в CI: короткий end-to-end пайплайн на временных темах.
13) Миграция «CRUD-интеграций → EDA»
1. Идентифицируйте доменные факты.
2. Внедрите outbox в исходные сервисы.
3. Опубликуйте минимальные доменные события и подключите 1–2 проекции.
4. Постепенно отключайте точечные синхронные интеграции, заменяя их подписками.
5. Введите Schema Registry и политику совместимости.
6. Расширяйте события add-only полями; ломки — только через новые типы.
14) Анти-паттерны
События = “DTO API” (слишком жирные, зависят от внутренней модели) — ломают потребителей.
Отсутствие Schema Registry и совместимости — «хрупкие» интеграции.
Публикация из кода и запись в БД не атомарны (нет outbox) — теряете события.
“Exactly-once везде” — высокая цена без выгоды; лучше at-least-once + идемпотентность.
Один «универсальный» ключ партиционирования → горячая партиция.
Реплей прямо в прод-проекцию — ломает онлайновые SLO.
15) Чек-лист внедрения (0–45 дней)
0–10 дней
Определить доменные события и их ключи (гранулы порядка).
Развернуть Schema Registry и утвердить стратегию совместимости.
Добавить outbox/inbox в 1–2 сервиса; минимальный CloudEvents-envelope.
11–25 дней
Ввести retry/DLQ, backoff, идемпотентность обработчиков.
Дашборды: lag/age/end-to-end; burn-rate алерты.
Документация событий (каталог), owner’ы и процессы ревью схем.
26–45 дней
Реплей/перестройка первой проекции; runbook реплея и backfill.
Политики безопасности (TLS, ACL, PII), ретеншн, GDPR-процедуры.
Регулярные chaos-и game-days для брокера и потребителей.
16) Метрики зрелости
100% доменных событий описаны схемами и зарегистрированы.
Outbox/inbox покрывают все продьюсеры/консьюмеры Tier-0/1.
SLO: p95 end-to-end latency и consumer lag в пределах целей ≥ 99%.
Реплей/Backfill осуществимы без даунтайма; есть проверенные runbook’и.
Версионирование: новые поля — без ломок; старые потребители не падают.
Безопасность: TLS+mTLS, ACL per topic, журналы доступа, политика PII/ретеншн.
17) Мини-сниппеты
Kafka Producer (надежная публикация, идеи):properties acks=all enable.idempotence=true max.in.flight.requests.per.connection=1 compression.type=zstd linger.ms=5
Consumer-обработчик (идемпотентность, псевдокод):
python if inbox.contains(event_id): return # дедуп process(event) # побочные эффекты детерминированы inbox.commit(event_id) # atomically with side-effect commit_offset()
RabbitMQ Retry через DLX (идея):
- `queue: tasks` → on nack → DLX `tasks.retry.1m` (TTL=60s) → возврат в `tasks`; далее `5m/15m`.
18) Заключение
EDA превращает интеграции в поток бизнес-фактов с четкими контрактами и управляемой согласованностью. Постройте фундамент: схемы+реестр, outbox/inbox, ключи порядка, идемпотентные обработчики, SLO и наблюдаемость, безопасный ретеншн и реплей. Тогда события станут вашим «источником истины» для масштабирования, аналитики и новых фич — без хрупких связей и ночных миграций.