# Event-driven архитектура

← [Раздел](README.md) · [Главная](../README.md)

## Цель

Понять **событийно-ориентированную** архитектуру: публикация фактов, подписчики, отличие от команд, eventual consistency и границы применимости.

## Предварительно

- [Асинхрон и очереди](asinhron-i-ocheredi.md)

## Время

**3–4 часа**

---

## Идея event-driven

Сервис **не вызывает** напрямую десять других API. Он **сообщает факт**: «Пункт добавлен». Подписчики **сами** решают, что делать: push, аналитика, поиск.

```mermaid
flowchart LR
  Lists[Svc Lists] -->|publish| Bus[Event Bus]
  Bus --> Push[Notifications]
  Bus --> Analytics[Analytics]
  Bus --> Search[Search Indexer]
```

**Слабая связность:** новый подписчик не меняет Lists.

---

## Событие vs команда

| | Команда | Событие |
|---|---------|---------|
| Имя | `SendEmail` | `InviteCreated` |
| Адресат | Один worker | Ноль или много |
| Смысл | Сделай | Уже случилось |
| Отмена | Можно отклонить | Факт в прошлом |

События именуют в **прошедшем времени**: `ListItemAdded`, `UserRegistered`.

---

## Структура события

```json
{
  "event_id": "evt_8f2a",
  "type": "ListItemAdded",
  "occurred_at": "2026-06-16T10:00:00Z",
  "aggregate_id": "list_42",
  "payload": {
    "item_id": "item_99",
    "name": "молоко"
  },
  "metadata": {
    "correlation_id": "req_abc"
  }
}
```

Версионируйте `type` или `schema_version` при эволюции.

---

## Event bus vs event log

| Event bus (Rabbit) | Event log (Kafka) |
|------------------|-------------------|
| Сообщение часто удаляется после ack | Хранит историю |
| Pub/sub, очереди | Replay, stream processing |
| Типичные задачи | Аналитика, CDC |

**CDC (Change Data Capture):** события из WAL PostgreSQL — отдельный продвинутый путь.

---

## Eventual consistency

После `ListItemAdded` поиск может обновиться через **секунды**. UI:

- оптимистичное обновление на клиенте;
- или polling до `indexed=true`.

| Допустимо | Недопустимо |
|-----------|-------------|
| Лаг индекса поиска | Двойное списание денег без strong consistency |

---

## CQRS (введение)

**Command Query Responsibility Segregation** — разные модели для записи и чтения.

| Write model | Read model |
|-------------|------------|
| Нормализованная БД | Денормализованный view / Elasticsearch |
| События обновляют read side | Быстрые запросы для UI |

Не обязателен на MVP; появляется при сложных отчётах и нагрузке на чтение.

---

## Saga (распределённая транзакция)

Заказ: резерв товара → оплата → доставка. При сбое оплаты — **компенсация**: отменить резерв.

```mermaid
sequenceDiagram
  participant O as Orders
  participant I as Inventory
  participant P as Payments
  O->>I: Reserve
  I-->>O: OK
  O->>P: Charge
  P-->>O: Fail
  O->>I: CancelReserve
```

Оркестрация (центральный координатор) vs хореография (только события) — trade-off сложности.

---

## Антипаттерны

| Проблема | Симптом |
|----------|---------|
| **Event hell** | 50 типов событий, никто не знает поток |
| **Chatty events** | Событие на каждый символ ввода |
| **Без схемы** | Ломаются consumers при добавлении поля |
| **Синхрон внутри handler** | Блокируете consumer, теряете смысл ED |

Используйте **schema registry** или JSON Schema для evolvable events.

---

## Когда НЕ нужен event-driven

- Монолит MVP с одной БД  
- Команда < 5 человек без опыта ops  
- Строгая синхронная отчётность в одном запросе  

Начните с **outbox + один consumer**; bus масштабируйте по метрикам.

---

## Наблюдаемость событий

| Метрика | Алерт |
|---------|-------|
| Consumer lag | > 1000 сообщений |
| Failed deserialization | > 0 |
| Event age p99 | > SLA |

Логируйте `event_id` + `correlation_id` в каждом handler.

---

## Практика

1. Для `UserRegistered` перечислите трёх подписчиков и их реакцию.  
2. Опишите eventual consistency для поиска по спискам (UX).  
3. Когда Saga обязательна vs одна БД?

---

## Самопроверка

1. Почему события именуют в прошедшем времени?  
2. Чем хореография отличается от оркестрации Saga?  
3. Что такое consumer lag?

---

## Дальше

→ [Контракты API](kontrakty-api.md)
