Перейти к содержимому

Event-Driven Architecture

Зачем знать: Event-Driven Architecture (EDA) — фундаментальный паттерн для loosely coupled систем. Вместо синхронных request-reply цепочек сервисы публикуют события и реагируют на чужие. EDA решает проблему cascade failures, позволяет fan-out, support multiple consumers, ассинхронные workflows. Middle Go-разработчик должен понимать разницу event vs command, choreography vs orchestration, schema evolution в Kafka, и почему “exactly-once” — это всегда tradeoff.

  1. Концепция: EDA, события, команды
  2. Глубже: choreography vs orchestration, schema evolution
  3. Gotchas
  4. Real cases
  5. Вопросы (25)
  6. Practice
  7. Источники

Event-Driven Architecture — стиль архитектуры, где компоненты обмениваются информацией через события (events). Producer публикует event, consumers подписываются и реагируют.

[Order Service] ──publishes──→ [Event Stream]
├─→ [Inventory Service] (reserves item)
├─→ [Notification Service] (sends email)
└─→ [Analytics Service] (records metric)

Ключевые свойства:

  • Loose coupling: producer не знает consumers (и наоборот).
  • Asynchronous: producer не ждёт ответа.
  • Fan-out: один event — много consumers.
  • Temporal decoupling: consumer может быть offline, обработает позже.
  • Easy extension: добавить нового consumer — без изменений producer.

Event: “что произошло” в прошедшем времени. Statement of fact.

  • OrderCreated, PaymentReceived, UserRegistered.
  • Не адресован конкретному получателю.
  • Multiple consumers могут реагировать.
  • Producer не знает, кто и как обработает.

Command: “что должно произойти” в императиве.

  • CreateOrder, ProcessPayment, SendEmail.
  • Адресован конкретному handler.
  • Может быть отвергнут (валидация).
  • Producer ожидает результат (или ack).
// Event
type OrderCreatedEvent struct {
OrderID string `json:"order_id"`
UserID string `json:"user_id"`
Total int64 `json:"total"`
CreatedAt time.Time `json:"created_at"`
}
// Command
type CreateOrderCommand struct {
UserID string `json:"user_id"`
Items []Item `json:"items"`
}

Когда event, когда command:

  • Event: что-то случилось, broadcast его. UserLoggedIn → analytics, security audit, recommendations.
  • Command: явное распоряжение. RefundOrder → конкретный handler.

Pub-Sub (publish-subscribe):

  • Один message — N consumers (все читают).
  • Topics, channels.
  • Kafka topic, NATS subject, Redis Pub/Sub, AWS SNS.
[Producer] → [Topic: order-events]
├──→ [Consumer A]
├──→ [Consumer B]
└──→ [Consumer C]

Queue (point-to-point):

  • Один message — один consumer (load-balanced).
  • RabbitMQ queue, SQS, NATS queue subscription.
[Producer] → [Queue: tasks]
├──→ [Worker 1] (message X)
└──→ [Worker 2] (message Y)

Kafka consumer groups — гибрид: внутри группы load balance, между группами fan-out.

Event notification: “что-то случилось, узнайте детали сами.”

{
"type": "OrderUpdated",
"order_id": "123"
}

Consumer должен сделать API call за деталями: GET /orders/123.

Event-carried state transfer: “что-то случилось, вот full snapshot.”

{
"type": "OrderUpdated",
"order_id": "123",
"user_id": "u1",
"items": [...],
"total": 1234,
"status": "paid"
}

Consumer не вызывает back, использует данные из event.

Trade-offs:

  • Notification: события маленькие, но coupling через API.
  • State transfer: события больше, consumer независим.

Чаще state transfer выигрывает. Loose coupling — главная цель EDA.

EDA — обмен сообщениями между сервисами. Event Sourcing — хранить state как лог событий (см. отдельный файл).

Они независимы: EDA можно делать без event sourcing (events publishit, но state хранят в БД). Event sourcing — выбор для модели данных.

Часто комбинируют: сервис строит state из событий internally + publishит external events для других сервисов.

Два подхода к распределённому workflow.

Choreography: сервисы реагируют на events independently. Нет центрального координатора.

[Order] → OrderCreated event
├─→ [Inventory] → ItemReserved event
│ └─→ [Payment] → PaymentCharged event
│ └─→ [Shipping] → OrderShipped
└─→ [Email] (parallel)

Pros: loose coupling, добавить шаг — добавить subscriber. Cons: workflow не виден в одном месте — сложно отслеживать.

Orchestration: центральный orchestrator вызывает сервисы по плану.

[Saga Orchestrator]
├─→ Reserve inventory
├─→ Charge payment (если успех)
├─→ Schedule shipping (если успех)
└─→ Compensate (если фейл)

Pros: workflow явный, проще debug. Cons: orchestrator — single point of complexity, tight coupling.

Когда что:

  • Choreography — для loose, неизвестных конечных consumers.
  • Orchestration — для критичных бизнес-процессов (платежи, заказы).

Kafka:

  • High throughput (миллионы msg/s per cluster).
  • Persistent log, replay.
  • Partitioning для scaling.
  • At-least-once / exactly-once (transactional).
  • Use case: event log, analytics pipeline, microservices integration.

NATS:

  • Lightweight, low latency (микросекунды).
  • JetStream — persistent layer (alternative to Kafka).
  • Subject-based routing с wildcards.
  • Use case: real-time microservices, IoT.

RabbitMQ:

  • AMQP protocol, flexible routing (exchange types).
  • Persistence, ack/nack.
  • Slightly slower than Kafka, easier for routing patterns.
  • Use case: task queues, RPC patterns.

Redis Streams:

  • Lightweight, in Redis.
  • Consumer groups.
  • Ограниченная persistence, durability vs Kafka.
  • Use case: simpler scenarios без отдельного broker.

События со временем меняются. Producers и consumers деплоятся независимо. Нужны правила backwards/forwards compat.

Backwards compatibility (BC): новый consumer может прочитать старые события. Forwards compatibility (FC): старый consumer может прочитать новые события.

Правила (для Protobuf):

  • Не удаляй поля (reserve номер).
  • Не меняй тип поля.
  • Не меняй tag номера.
  • Новые поля — optional.

Avro / Protobuf vs JSON:

  • Avro: schema-first, evolution rules built-in.
  • Protobuf: бинарный, fast, mature evolution semantics.
  • JSON: human-readable, но schema enforcement требует JSON Schema или Cloudevents.

Confluent Schema Registry, Karapace, AWS Glue Schema Registry.

Функции:

  • Хранение схем (по subject = topic).
  • Versioning.
  • Compat rules (backward, forward, full).
  • Producer регистрирует schema перед publish.
  • Consumer fetch’ит schema по ID.

Subject naming strategies:

  • TopicName: orders-value (одна схема на topic).
  • RecordName: OrderCreatedEvent (схема по типу события).
  • TopicRecordName: orders-OrderCreatedEvent (multiple типов в одном topic).

At-most-once: возможна потеря, нет дублей. Producer не ретраит. At-least-once: дубли возможны, потерь нет. Producer retries, consumer должен быть идемпотентен. Exactly-once: дублей нет, потерь нет.

Kafka exactly-once (EOS, exactly-once semantics):

  • enable.idempotence=true — producer не создаёт дубли при retry.
  • transactional.id — атомарные транзакции producer.
  • isolation.level=read_committed — consumer читает только committed.
  • Это EOS внутри Kafka. Side effects (HTTP, БД) требуют отдельной идемпотентности.

Consumer должен корректно обработать дубли. См. файл 20 (идемпотентность).

Pattern:

func handleOrderCreated(event OrderCreatedEvent) error {
// Проверить, не обработано ли уже
processed, err := db.IsProcessed(event.EventID)
if err != nil { return err }
if processed { return nil } // skip
// Выполнить
if err := businessLogic(event); err != nil { return err }
// Маркировать processed
return db.MarkProcessed(event.EventID)
}

Pitfall: между business logic и MarkProcessed может упасть. Решение: транзакция (если БД одна) или Outbox pattern.

См. middle 1 файл по Saga/Outbox. Кратко:

Producer пишет event в одну БД-транзакцию вместе с бизнес-данными. Отдельный worker читает таблицу outbox и публикует в broker. Гарантия — event опубликован тогда и только тогда, когда транзакция commit’нута.

В sync system stack trace показывает весь call chain. В EDA — нет.

Проблемы:

  • “Где сообщение?” — может быть в Kafka, может потеряно, может обработано.
  • “Какая последовательность?” — Distributed trace без correlation ID — мука.
  • “Почему медленно?” — latency скрыта в очередях.

Решения:

  • Distributed tracing (OpenTelemetry): trace ID пробрасывается через broker (header).
  • Event log search: агрегатор всех событий по correlation ID (Loki, Elastic).
  • Event timeline UI: UI для просмотра event flow.
  • Dead letter queue (DLQ): failed events для post-mortem.
// Trace ID через Kafka headers
msg := &kafka.Message{
Topic: "orders",
Value: payload,
Headers: []kafka.Header{
{Key: "trace-id", Value: []byte(spanContext.TraceID().String())},
},
}

Pros:

  • Scalability — independent consumers.
  • Resilience — broker buffers, consumer offline OK.
  • Loose coupling — easy extension.
  • Audit — events — natural log.
  • Multi-language — broker — language-agnostic.

Cons:

  • Complexity — debug, monitoring, schema.
  • Eventual consistency — sync request-reply гарантирует, async — нет.
  • Operational overhead — broker cluster, schema registry.
  • Learning curve — команда должна понимать.
  • Latency — async добавляет ms-s.

// Order Service
func (s *OrderService) CreateOrder(ctx context.Context, req CreateOrderRequest) error {
order := &Order{...}
if err := s.repo.Save(ctx, order); err != nil { return err }
// Outbox publish
s.outbox.Publish(ctx, OrderCreatedEvent{
OrderID: order.ID, UserID: order.UserID, Total: order.Total,
})
return nil
}
// Inventory Service
func (s *InventoryService) OnOrderCreated(ctx context.Context, event OrderCreatedEvent) error {
if err := s.reserveItems(ctx, event); err != nil {
s.publish(InventoryReservationFailed{OrderID: event.OrderID, Reason: err.Error()})
return err
}
s.publish(InventoryReserved{OrderID: event.OrderID})
return nil
}
// Payment Service
func (s *PaymentService) OnInventoryReserved(ctx context.Context, event InventoryReserved) error {
if err := s.charge(ctx, event); err != nil {
s.publish(PaymentFailed{OrderID: event.OrderID})
// compensation: release inventory
s.publish(ReleaseInventory{OrderID: event.OrderID})
return err
}
s.publish(PaymentCharged{OrderID: event.OrderID})
return nil
}
// Compensation: Inventory reacts to PaymentFailed
func (s *InventoryService) OnPaymentFailed(ctx context.Context, event PaymentFailed) error {
return s.release(ctx, event.OrderID)
}
type CreateOrderSagaOrchestrator struct {
inventory *InventoryClient
payment *PaymentClient
shipping *ShippingClient
repo *SagaRepository
}
func (o *Orchestrator) Execute(ctx context.Context, cmd CreateOrderCommand) error {
saga := o.repo.NewSaga(cmd)
// Step 1: Reserve
if err := o.inventory.Reserve(ctx, cmd.OrderID, cmd.Items); err != nil {
saga.MarkFailed(err)
return err
}
saga.MarkStep("reserved")
// Step 2: Pay
if err := o.payment.Charge(ctx, cmd.UserID, cmd.Total); err != nil {
// Compensate
o.inventory.Release(ctx, cmd.OrderID)
saga.MarkFailed(err)
return err
}
saga.MarkStep("paid")
// Step 3: Ship
if err := o.shipping.Schedule(ctx, cmd.OrderID, cmd.Address); err != nil {
// Compensate in reverse
o.payment.Refund(ctx, cmd.OrderID)
o.inventory.Release(ctx, cmd.OrderID)
saga.MarkFailed(err)
return err
}
saga.MarkStep("shipped")
saga.MarkCompleted()
return nil
}

Для production: использовать workflow engines (Temporal, Cadence) которые managae state и retries.

import "go.temporal.io/sdk/workflow"
func CreateOrderWorkflow(ctx workflow.Context, input OrderInput) error {
ao := workflow.ActivityOptions{
StartToCloseTimeout: 10 * time.Second,
RetryPolicy: &temporal.RetryPolicy{
MaximumAttempts: 3,
},
}
ctx = workflow.WithActivityOptions(ctx, ao)
if err := workflow.ExecuteActivity(ctx, ReserveInventory, input).Get(ctx, nil); err != nil {
return err
}
defer func() {
if /* error happened */ {
workflow.ExecuteActivity(ctx, ReleaseInventory, input.OrderID)
}
}()
if err := workflow.ExecuteActivity(ctx, ChargePayment, input).Get(ctx, nil); err != nil {
return err
}
defer func() {
if /* error */ {
workflow.ExecuteActivity(ctx, RefundPayment, input.OrderID)
}
}()
return workflow.ExecuteActivity(ctx, ScheduleShipping, input).Get(ctx, nil)
}

Temporal handles retries, state, timeouts автоматически. Workflow code — durable.

Producer:

import "github.com/riferrei/srclient"
client := srclient.CreateSchemaRegistryClient("http://schema-registry:8081")
schema, _ := client.GetLatestSchema("orders-value")
// Encode message with schema ID
data := encodeAvro(payload, schema.Codec())
header := []byte{0}
binary.BigEndian.PutUint32(idBytes, uint32(schema.ID()))
message := append(header, idBytes...)
message = append(message, data...)
producer.Produce(&kafka.Message{Value: message})

Consumer:

// Read schema ID from first 5 bytes
schemaID := binary.BigEndian.Uint32(msg.Value[1:5])
schema, _ := client.GetSchema(int(schemaID))
payload := decodeAvro(msg.Value[5:], schema.Codec())
syntax = "proto3";
package events;
message OrderCreated {
string order_id = 1;
string user_id = 2;
int64 total_cents = 3;
google.protobuf.Timestamp created_at = 4;
repeated Item items = 5;
}
message Item {
string product_id = 1;
int32 quantity = 2;
}

Migration rules:

  • Add field — OK (older consumer ignores).
  • Rename — OK (field number matters).
  • Remove — reserve number (reserved 5;).
  • Type change — break compatibility.

Стандартизованный envelope для events. Поддерживается Kafka, NATS, AWS, GCP, Azure.

{
"specversion": "1.0",
"type": "com.example.order.created",
"source": "/orders",
"id": "evt-123",
"time": "2026-01-01T00:00:00Z",
"datacontenttype": "application/json",
"subject": "order/123",
"data": {
"order_id": "123",
"total": 1000
}
}

Преимущества: cross-platform, цвет single envelope, libraries для many languages.

type IdempotentConsumer struct {
db *pgxpool.Pool
handler func(event Event) error
}
func (c *IdempotentConsumer) Handle(ctx context.Context, event Event) error {
tx, err := c.db.BeginTx(ctx, pgx.TxOptions{})
if err != nil { return err }
defer tx.Rollback(ctx)
// INSERT idempotency, error if duplicate
_, err = tx.Exec(ctx,
`INSERT INTO processed_events (event_id, processed_at) VALUES ($1, NOW())`,
event.ID)
if err != nil {
if isUniqueViolation(err) {
return nil // already processed
}
return err
}
// Business logic в той же транзакции
if err := c.handler(event); err != nil { return err }
return tx.Commit(ctx)
}

Условие: handler работает с той же БД. Если handler делает HTTP call — отдельная idempotency на HTTP уровне.

Pattern 1: Versioned topic.

  • orders.v1, orders.v2. Producer пишет в v2, consumers подписываются на нужную версию.
  • Pros: чёткое разделение.
  • Cons: producers поддерживают обе версии в transition period.

Pattern 2: Versioned schema в одной topic.

  • Schema registry хранит несколько версий.
  • Consumer fetch’ит schema по ID и upcast’ит.
  • Backwards compat обязательна.

Pattern 3: Upcaster.

  • Старые события transformируются “на лету” в новый формат.
  • Полезно для event sourcing replay.

Correlation IDs:

type Envelope struct {
EventID string
CorrelationID string // одинаковый для всей "цепочки"
CausationID string // ID события-причины
OccurredAt time.Time
Payload any
}

CorrelationID — для logical chain (saga). CausationID — для linking события с предыдущим event.

С этим UI может построить full timeline.

Distributed tracing через broker:

  • Producer добавляет traceparent header (W3C).
  • Consumer extract’ит и продолжает trace.
  • OpenTelemetry instrumentation для Kafka, NATS — есть.

Если consumer не может обработать event (после N retries) — отправить в DLQ.

[Main Topic] → [Consumer] (3 attempts fails)
[DLQ: orders-dlq]
[Manual / Replay process]

Pattern:

  • N retries с exponential backoff.
  • После N → publish в DLQ topic.
  • Включить original event + error context.
  • Operator анализирует и решает: replay, fix, drop.
func handle(ctx context.Context, event Event) error {
for attempt := 1; attempt <= 3; attempt++ {
if err := process(event); err != nil {
time.Sleep(time.Duration(attempt) * time.Second)
continue
}
return nil
}
return publishToDLQ(event, lastErr)
}

⚠️ Event != Command. Если ваш “event” имеет single consumer и явный intent — это command. Smell: SendEmailEvent (это command).

⚠️ At-least-once = duplicate events. Каждый consumer должен быть идемпотентен. Tracking processed_event_ids.

⚠️ Exactly-once в Kafka — внутри Kafka. HTTP calls, БД записи в другие системы — не покрыты. Уделяй внимание side effects.

⚠️ Out-of-order delivery. Kafka гарантирует order только внутри partition. Между partitions order не гарантирован. Используй partition key (например, orderID).

⚠️ Schema breaking changes. Producer выкатил new schema без backwards compat → consumer не парсит. Schema registry с compat rules — обязательно.

⚠️ Event-carried state too big. Если event 10MB — broker заваливается. Используй reference (notification + API call) или CDC patterns.

⚠️ PII в events. Sensitive данные в Kafka — compliance (GDPR). Encryption at rest, ACLs на topics, audit logs.

⚠️ Consumer lag. Если consumer медленный — events накапливаются. Monitoring lag (Burrow для Kafka). Auto-scaling consumers.

⚠️ Rebalance storms. В Kafka consumer group часто rebalance → паузы. Stable consumers с правильным session.timeout.

⚠️ Choreography → “spaghetti.” Без документации event flow трудно отследить. Используй event maps, BPMN.

⚠️ Orchestrator monolith. Большой orchestrator сам становится monolith. Разбивай на бизнес-процессы.

⚠️ Dead letter ad infinitum. Если DLQ не мониторится — теряются события. Alert на DLQ size.

⚠️ Eventually consistent UX. “Заказ создан, но не показывается в списке секунду.” Учитывай в UI: оптимистический update, polling.

⚠️ Replay events опасно. Если consumer не идемпотентен — replay создаст дубли (вторые charges, double emails). Toggle “replay mode” или dry-run.

⚠️ Compaction в Kafka. Compacted topic хранит latest value per key. Полезно для state, но не для events. Не путай.

⚠️ Tombstones. Для удаления в compacted topic — null value. Consumer должен handle null.

⚠️ Distributed transaction is hard. Saga — eventually consistent, не atomic. Если требуется atomicity — переосмысли границы сервиса.


Uber — fast ETA с Kafka. Trip events → Kafka. Многочисленные consumers: ETA prediction (ML), pricing, analytics, fraud detection. Petabytes/day, миллионы msg/s.

Netflix — event-driven re-architecture. Когда переходили с DVD-by-mail на streaming, событийная архитектура помогла. View events, recommendations pipeline, A/B test events.

Stripe — event-driven webhooks. External events для customers. Stripe гарантирует at-least-once delivery. Customers обязаны делать idempotent handlers. Stripe-Signature header для verification.

LinkedIn — Kafka birth. Kafka рождён в LinkedIn 2011 для unification log/event pipelines. Сейчас open source CNCF.

Shopify — Kafka для inventory updates. Когда заказ → inventory event → multiple downstream (search, recommendations, warehouses). EDA для loose coupling.

Yandex.Eats — order pipeline. Order создан → publish в Kafka. Consumers: courier matching, restaurant POS, ETA calculation, customer notifications. Independent scaling каждого.

Tinkoff — payment processing. Платёжные события через Kafka. Outbox pattern в каждом сервисе. Idempotent consumers.

Cloudflare — analytics pipeline. Edge events (миллиарды/день) → Kafka → ClickHouse. Real-time analytics, abuse detection.

Discord — message delivery. Cassandra для storage + Kafka для real-time fanout (миллионы concurrent users в гильдиях).

Slack — real-time messaging. Messages через broker → fan-out к connected clients. Edge servers с WebSocket connections.

Avito — search indexing. Listing changes → event → search reindex (Elasticsearch). Decoupled: листинг и search независимы.

Mercari — eventual consistency в каталоге. Item posted → event → search index update (eventual). UX optimistic update.

Kafka Streams in Confluent customers. Real-time aggregations, joins, windowing. Используется banks (fraud detection), retailers (real-time inventory).

NATS at Synadia / Adidas. Adidas использует NATS для глобального real-time pricing/inventory. NATS JetStream для durability.

Saga orchestrator с Temporal: Coinbase, Stripe, Snap используют Temporal для workflows. Order processing, payment retries, KYC pipelines.


Q1: Что такое Event-Driven Architecture? A: Архитектурный стиль, где компоненты обмениваются через события. Loose coupling, async, fan-out, temporal decoupling. Producer не знает consumers.

Q2: Event vs Command — в чём разница? A: Event — “что произошло” (past), нет адресата, multiple consumers. Command — “что должно произойти” (imperative), конкретный handler, может быть отвергнут.

Q3: Pub-Sub vs Queue — когда что? A: Pub-Sub — один message всем consumers (broadcast). Queue — один message одному worker (load balance). Kafka consumer groups — гибрид.

Q4: Event notification vs event-carried state transfer? A: Notification — small event, consumer вызывает API за деталями. State transfer — full data в event. State transfer чаще выигрывает (loose coupling).

Q5: EDA vs Event Sourcing — это одно и то же? A: Нет. EDA — обмен сообщениями. Event Sourcing — хранение state как event log. Можно комбинировать, но независимы.

Q6: Choreography vs Orchestration — в чём разница? A: Choreography — сервисы реагируют independently на events, нет координатора. Orchestration — центральный orchestrator вызывает по плану.

Q7: Когда выбрать choreography? A: Loose coupling, unknown consumers, простой workflow. Добавить шаг — добавить subscriber.

Q8: Когда выбрать orchestration? A: Сложный workflow, нужна видимость прогресса, явные compensations. Critical business processes (платежи, заказы).

Q9: Какие message brokers популярны? A: Kafka (high throughput, persistent log), NATS (low latency, JetStream), RabbitMQ (flexible routing AMQP), Redis Streams (lightweight).

Q10: At-least-once vs exactly-once? A: At-least-once — дубли возможны, потерь нет. Exactly-once — без дублей и потерь. Exactly-once в Kafka — внутри Kafka (transactional). Side effects требуют отдельной идемпотентности.

Q11: Как сделать consumer идемпотентным? A: Хранить processed_event_ids. Перед обработкой проверять. Wrap всё в transaction если возможно. Или Outbox pattern.

Q12: Что такое Outbox pattern? A: События пишутся в одну транзакцию с бизнес-данными. Отдельный worker читает таблицу outbox и публикует в broker. Гарантия atomicity.

Q13: Schema evolution в EDA — какие правила? A: Backwards compat (new consumer reads old). Forward compat (old consumer reads new). Не удаляй поля, не меняй типы, новые — optional.

Q14: Что такое Schema Registry? A: Сервис хранения и versioning схем (Confluent, Karapace). Compat rules (backward, forward, full). Producer регистрирует, consumer fetch’ит по ID.

Q15: Avro vs Protobuf vs JSON для events? A: Avro — schema-first, evolution built-in. Protobuf — бинарный, быстрый, mature evolution. JSON — human-readable, нужен JSON Schema/Cloudevents для контроля.

Q16: Что такое CloudEvents? A: CNCF спецификация envelope для events. Поля: type, source, id, time, data. Cross-platform standard.

Q17: Что такое Dead Letter Queue? A: Topic для failed events после N retries. Operator анализирует, replay или fix. Защита от blocking main pipeline.

Q18: Как делать distributed tracing в EDA? A: OpenTelemetry: trace ID в headers (Kafka headers, RabbitMQ headers). Consumer extract’ит и продолжает span.

Q19: Что такое correlation ID и causation ID? A: Correlation ID — единый для logical chain (saga). Causation ID — ID события-причины. Помогают reconstruct event timeline.

Q20: Какие проблемы у EDA? A: Eventual consistency, complexity debug, schema evolution, operational overhead (broker cluster), learning curve, ordering guarantees.

Q21: Как обеспечить order в Kafka? A: Только внутри partition. Используй partition key (orderID) — все события для одного order в одну partition.

Q22: Что такое compaction в Kafka? A: Topic хранит только последнее value per key (вместо всех). Полезно для state snapshots, не для event log.

Q23: Дубли событий — нормально? A: В at-least-once — да, нормально. Consumer должен быть идемпотентен. В at-most-once — нет дублей, но потери возможны.

Q24: Как тестировать EDA? A: Unit (handler логика), integration (с реальным broker, например testcontainers), contract test (producer-consumer schema), E2E через event flow.

Q25: Сколько событий в секунду может обработать Kafka? A: Производительность зависит от размера, кол-ва partitions, hardware. Уровень: миллионы msg/s на cluster (3-10 nodes), single partition — десятки тыс. msg/s.


  1. Реализуй pub-sub on Kafka. Producer публикует OrderCreated, 2 consumers (Inventory, Notification) обрабатывают независимо.

  2. Choreography saga. 3 сервиса (Order, Inventory, Payment) реагируют на events. Симулируй PaymentFailed → Compensation (Release inventory).

  3. Orchestration saga с Temporal. Тот же workflow, но через Temporal SDK. Запусти Temporal locally (docker-compose).

  4. Idempotent consumer. Storage processed_event_ids в Postgres. Симулируй duplicate delivery, покажи отсутствие side effects.

  5. Schema registry на практике. Confluent Schema Registry в docker. Avro schema для OrderCreated. Producer регистрирует, consumer decodes.

  6. Backwards compat test. v1 schema → publish 100 events. Deploy v2 (с extra field). Consumer на v1 schema должен по-прежнему читать.

  7. DLQ implementation. Consumer с 3 retries, потом publish в DLQ topic. Отдельный consumer DLQ — для inspection.

  8. Distributed tracing. OpenTelemetry с Kafka. Trace ID пробрасывается через headers. Jaeger UI показывает чейн.

  9. Outbox pattern. Transactional outbox table. Worker debezium-like читает changes и publishит в Kafka.

  10. Compare brokers. Реализуй one and the same scenario в Kafka, NATS, RabbitMQ. Сравни latency, throughput, complexity.


  1. Martin Fowler. “What do you mean by ‘Event-Driven’?” 2017. https://martinfowler.com/articles/201701-event-driven.html
  2. Chris Richardson. “Microservices Patterns.” Manning, 2018. — главы Saga, Outbox.
  3. Confluent. “Kafka: The Definitive Guide.” 2nd ed. — Gwen Shapira et al.
  4. Greg Young. “CQRS Documents.” (event sourcing foundations).
  5. CloudEvents specification. https://cloudevents.io/
  6. Confluent Schema Registry docs. https://docs.confluent.io/platform/current/schema-registry/
  7. Temporal documentation. https://docs.temporal.io/
  8. NATS documentation. https://docs.nats.io/
  9. Eventuate Tram framework. https://eventuate.io/
  10. Uber Engineering. “Designing Schemaless.” 2016. https://www.uber.com/blog/schemaless-part-one-mysql-datastore/
  11. LinkedIn. “Apache Kafka, Samza, and the Unix Philosophy of Distributed Data.” 2014.
  12. Vaughn Vernon. “Reactive Messaging Patterns with the Actor Model.” 2015.