Событийно-ориентированная архитектура, или Event-driven Architecture, — это подход к построению информационных систем, при котором компоненты взаимодействуют преимущественно через события. Один сервис сообщает, что в системе что-то произошло, а другие компоненты самостоятельно решают, нужно ли им реагировать на это событие.
Например, после оформления заказа сервис заказов публикует событие OrderCreated. Сервис уведомлений отправляет письмо клиенту, склад резервирует товар, аналитическая система обновляет показатели, а CRM получает информацию о новой покупке.
Главная идея заключается в том, что отправителю события не обязательно напрямую знать всех его получателей.
Событийно-ориентированная архитектура отделяет факт произошедшего события от действий, которые другие компоненты выполняют в ответ.
Что такое событие
Событие — это сообщение о факте, который уже произошел в системе.
Примеры событий:
- пользователь зарегистрирован;
- заказ создан;
- платеж подтвержден;
- товар отгружен;
- документ изменен;
- пароль сброшен;
- остаток товара достиг нуля.
Название события обычно формулируют в прошедшем времени, поскольку оно описывает уже произошедшее изменение состояния.
Пример события
{
"event_type": "OrderCreated",
"event_id": "evt-90125",
"order_id": 501,
"customer_id": 125,
"created_at": "2026-08-17T10:00:00Z"
}В сообщении указываются тип события, идентификатор, время и данные, которые нужны потенциальным Consumers.
Как работает событийно-ориентированная архитектура
Упрощенная схема выглядит так:
Order Service ↓ OrderCreated ↓ Message Broker ↓ ↓ ↓ Email Warehouse Analytics
Order Service публикует событие, а другие системы получают его через Message Broker или Event Streaming Platform.
Каждый Consumer обрабатывает событие независимо.
Основные компоненты Event-driven Architecture
| Компонент | Назначение |
|---|---|
| Producer | Создает и публикует событие |
| Event | Описывает произошедший факт |
| Broker | Передает или хранит события |
| Consumer | Получает событие и реагирует на него |
| Event Schema | Определяет структуру сообщения |
Producer
Producer — компонент, который создает событие.
Например, Payment Service после успешного платежа публикует PaymentCompleted.
Producer сообщает о факте, но в хорошо разделенной архитектуре не должен знать подробности всех последующих действий.
Consumer
Consumer — компонент, который подписывается на события и выполняет определенную реакцию.
На одно событие могут реагировать несколько Consumers.
Например, PaymentCompleted получают бухгалтерская система, сервис уведомлений и аналитика.
Message Broker
Message Broker является промежуточным компонентом между Producer и Consumer.
Он принимает сообщения и передает их соответствующим получателям.
Для этой роли могут использоваться RabbitMQ, Apache Kafka и другие платформы, хотя их модели работы существенно различаются.
Событийно-ориентированная архитектура и RabbitMQ
RabbitMQ удобен, когда архитектуре нужны очереди сообщений, развитая маршрутизация, Acknowledgements и распределение задач между Workers.
Событие может попасть в Exchange, а затем маршрутизироваться в несколько Queues.
Каждый сервис получает собственную очередь и независимо обрабатывает сообщения.
Событийно-ориентированная архитектура и Apache Kafka
Apache Kafka часто используется как распределенный журнал событий.
Producer публикует события в Topic, а независимые Consumer Groups читают поток.
Kafka особенно полезна, если требуется большой Throughput, хранение истории и возможность повторного чтения событий.
Event и Command
Событие важно отличать от команды.
| Event | Command |
|---|---|
| Описывает произошедший факт | Просит выполнить действие |
| OrderCreated | CreateInvoice |
| Может иметь много Consumers | Часто ориентирована на конкретного обработчика |
| Producer не определяет все реакции | Отправитель ожидает определенное действие |
Смешивание команд и событий усложняет понимание системы.
Event Notification
Event Notification — простой вариант события, которое сообщает, что что-то изменилось.
Сообщение может содержать только идентификатор сущности.
{
"event_type": "CustomerUpdated",
"customer_id": 125
}Consumer после получения самостоятельно запрашивает актуальное состояние через API или Database.
Event-carried State Transfer
Другой подход — передавать вместе с событием часть нового состояния.
{
"event_type": "CustomerUpdated",
"customer_id": 125,
"name": "Иван Петров",
"status": "active"
}Consumer может обновить собственное представление без дополнительного синхронного запроса.
Недостаток — увеличение Payload и зависимость Consumers от Event Schema.
Слабая связанность
Одна из главных целей Event-driven Architecture — Loose Coupling.
Order Service не обязан знать адрес Email Service или Analytics Service.
Он публикует OrderCreated, а подписчики подключаются независимо.
Это упрощает добавление новых реакций на существующее событие.
Преимущества слабой связанности
Если компания решает добавить систему лояльности, Order Service не обязательно изменять.
Новый Loyalty Service подписывается на OrderCreated и начисляет бонусы.
Таким образом, архитектура легче развивается по мере появления новых интеграций.
Асинхронность
Событийные системы часто работают асинхронно.
Producer не ждет, пока все Consumers завершат обработку.
Например, оформление заказа может завершиться за сотни миллисекунд, а отправка email произойдет немного позже.
Зачем нужна асинхронность
Асинхронность уменьшает зависимость времени ответа одного сервиса от скорости всех остальных компонентов.
Если CRM временно работает медленно, пользователь все равно может оформить заказ, а интеграция выполнится через очередь позже.
Event-driven и синхронная архитектура
| Синхронное взаимодействие | Event-driven |
|---|---|
| Сервис вызывает другой сервис | Сервис публикует событие |
| Обычно ожидается Response | Обработка может произойти позже |
| Более простая трассировка | Более слабая связанность |
| Сбой получателя влияет на вызывающего | Broker может временно буферизовать события |
На практике современные системы часто комбинируют оба подхода.
Когда использовать REST, а когда события
Если сервису необходимо прямо сейчас узнать текущий баланс пользователя, синхронный API часто естественнее.
Если нужно сообщить, что баланс изменился, событие BalanceChanged может быть удобнее.
Event-driven Architecture не должна превращать любой запрос в асинхронное событие.
Event-driven и gRPC
gRPC хорошо подходит для прямого взаимодействия сервисов и Request/Response.
События лучше подходят для распространения информации о произошедших изменениях.
Например, Inventory Service предоставляет gRPC-метод получения текущего остатка и публикует StockChanged при его изменении.
Event-driven и Webhook
Webhook также является реакцией на событие, но обычно реализуется как HTTP-вызов известного получателя.
Он особенно удобен для интеграции с внешними системами.
Внутри компании событие может сначала попасть в Broker, а отдельный Consumer уже отправит Webhook партнеру.
Микросервисы и события
Event-driven Architecture часто используется вместе с Microservices.
Каждый сервис владеет собственными данными и публикует события о значимых изменениях.
Другие сервисы не должны напрямую изменять его Database.
Database per Service
В микросервисной архитектуре отдельный сервис часто имеет собственную базу.
Order Service хранит заказы, Payment Service — платежи, Inventory Service — остатки.
События позволяют синхронизировать бизнес-процессы без общего доступа ко всем таблицам.
Проблема распределенной транзакции
Если одна бизнес-операция затрагивает несколько сервисов, обычная ACID Transaction одной базы уже не охватывает весь процесс.
Например, заказ нужно создать, оплатить и зарезервировать товар.
Каждый сервис может успешно выполнить свою локальную транзакцию, но один из последующих шагов может завершиться ошибкой.
Saga Pattern
Saga разбивает распределенный процесс на последовательность локальных транзакций.
Если один этап не выполнен, запускаются компенсационные действия.
Например:
- Order Service создает заказ.
- Payment Service списывает деньги.
- Inventory Service пытается зарезервировать товар.
- Если товара нет, Payment Service получает команду на возврат.
Saga помогает управлять Eventually Consistent бизнес-процессами.
Choreography
При Choreography сервисы реагируют на события друг друга без центрального координатора.
OrderCreated запускает Payment, PaymentCompleted — резервирование товара, InventoryReserved — отправку подтверждения.
Преимущество — слабая связанность.
Недостаток — сложнее увидеть весь бизнес-процесс целиком.
Orchestration
При Orchestration существует компонент, который координирует шаги процесса.
Он отправляет команды сервисам и принимает результаты.
Так легче видеть общий Workflow, но появляется отдельный Coordinator.
Choreography и Orchestration
| Choreography | Orchestration |
|---|---|
| Нет центрального координатора | Есть управляющий компонент |
| Сервисы реагируют на события | Coordinator управляет шагами |
| Выше децентрализация | Проще контролировать сложный Workflow |
Transactional Outbox
Одна из ключевых проблем Event-driven Architecture — надежно согласовать изменение Database и публикацию события.
Представим, что Order Service записал заказ в PostgreSQL, но публикация OrderCreated завершилась ошибкой.
Заказ существует, а Consumers о нем не знают.
Transactional Outbox решает эту проблему: заказ и запись о событии сохраняются в одной ACID Transaction.
Как работает Outbox
- Backend открывает Database Transaction.
- Создает бизнес-запись.
- Создает запись в Outbox Table.
- Выполняет COMMIT.
- Отдельный Worker читает Outbox.
- Публикует Event в Broker.
- Отмечает событие как отправленное.
Так вероятность потери события значительно уменьшается.
Change Data Capture
Change Data Capture, или CDC, — подход к получению событий на основании изменений в Database.
Например, изменения PostgreSQL могут преобразовываться в поток сообщений Apache Kafka.
CDC используется для интеграций, аналитики и синхронизации данных.
Event Schema
Событие является контрактом между Producer и Consumers.
Если Producer неожиданно изменит тип поля или удалит его, старые Consumers могут перестать работать.
Поэтому Event Schema необходимо проектировать и версионировать.
Schema Evolution
Система развивается, и сообщения тоже меняются.
Например, в OrderCreated появляется новое поле source.
Безопаснее добавлять необязательные поля и сохранять совместимость с уже работающими Consumers.
Backward Compatibility
Backward Compatibility означает, что новая версия события остается понятна существующим Consumers.
Это особенно важно, если десятки команд выпускают сервисы независимо.
Event Versioning
При значительных изменениях можно использовать явное поле версии или новый тип события.
Необходимо избегать ситуации, когда одно и то же название Event внезапно получает совершенно другой смысл.
Идемпотентность
В распределенной системе одно событие может быть доставлено повторно.
Consumer должен быть готов обработать Duplicate без неправильного дополнительного эффекта.
Например, повторное PaymentCompleted не должно дважды начислить бонусы.
Как сделать Consumer идемпотентным
Сообщению присваивают Event ID, а Consumer сохраняет обработанные идентификаторы.
Другой вариант — использовать UNIQUE Constraint на бизнес-операцию.
Конкретная реализация зависит от типа данных и СУБД.
At-least-once Delivery
Многие надежные системы строятся с учетом At-least-once Delivery.
Сообщение стараются не потерять, но при сбое оно может прийти повторно.
Поэтому Duplicate считается нормальным техническим сценарием.
At-most-once Delivery
При At-most-once событие не обрабатывается повторно, но потенциально может быть потеряно.
Такая модель допустима для некритичных метрик или телеметрии, но обычно не подходит для платежных операций.
Exactly-once
Exactly-once означает стремление получить один логический эффект от события.
На практике обеспечить это во всей цепочке Broker, Consumer, Database и внешнего API сложно.
Поэтому архитектура часто достигает нужного бизнес-результата через At-least-once и идемпотентность.
Порядок событий
Некоторым бизнес-процессам важен порядок.
Например, OrderCancelled не должен обрабатываться раньше OrderCreated для одного заказа.
Message Broker может предоставлять порядок в определенных пределах, например внутри Kafka Partition.
Но глобальный порядок всех событий в распределенной системе обычно дорого поддерживать.
Event Key
Для событий одной сущности полезно использовать общий Key.
Например, order_id помогает направить связанные сообщения в одну Kafka Partition.
Так их относительный порядок становится проще сохранять.
Late Events
Событие может прийти с задержкой из-за сети, Retry или недоступности Consumer.
Поэтому система не всегда должна считать последнее полученное сообщение самым новым по времени бизнеса.
Timestamp и версия объекта помогают правильно обрабатывать такие случаи.
Out-of-order Events
В распределенной системе события могут прийти не в ожидаемом порядке.
Consumer должен понимать, критично ли это для конкретной операции.
Для некоторых процессов можно использовать Version, Sequence Number или проверку текущего состояния.
Retry
При временной ошибке Consumer может повторить обработку.
Например, внешнее API временно недоступно.
Retry должен иметь ограничение по количеству попыток и желательно увеличивающийся интервал между ними.
Dead Letter Queue
Если сообщение стабильно не обрабатывается, его отправляют в Dead Letter Queue.
Это позволяет основному потоку продолжить работу.
Проблемное событие затем анализируется отдельно.
Poison Message
Poison Message — событие, которое постоянно вызывает ошибку.
Например, из-за поврежденного Payload или несовместимой Schema.
Бесконечная повторная обработка может остановить Consumer, поэтому такие сообщения изолируют.
Backpressure
Backpressure возникает, когда Producers создают события быстрее, чем Consumers могут их обработать.
Broker временно накапливает сообщения.
Если отставание продолжает расти, необходимо масштабировать Consumers или оптимизировать обработку.
Consumer Lag
Для потоковых систем вроде Kafka важным показателем является Consumer Lag.
Он показывает, насколько обработчик отстает от актуального конца потока.
Большой Lag означает, что события обрабатываются с задержкой.
Event-driven и масштабирование
Асинхронная очередь позволяет независимо масштабировать отдельные этапы.
Если обработка изображений стала узким местом, можно увеличить количество Image Workers, не масштабируя Order Service.
Это одно из важных преимуществ архитектуры.
Буферизация нагрузки
Broker способен временно сглаживать всплески.
Например, в период распродажи за минуту создается 50 000 заказов.
Backend быстро публикует события, а менее критичные Consumers обрабатывают их постепенно.
Важно только контролировать, чтобы накопление не стало постоянным.
Event-driven и Data Pipeline
События могут использоваться не только для бизнес-логики, но и для перемещения данных.
Website → Events → Kafka → Data Warehouse
Пользовательские действия поступают в поток, а аналитические Consumers загружают их в хранилище.
Event-driven и аналитика
Событийная модель удобна для near real-time аналитики.
Например, компания может почти сразу видеть количество заказов, платежей и активных пользователей.
Отдельный Consumer обрабатывает поток, не нагружая основную OLTP-базу тяжелыми отчетами.
Stream Processing
Stream Processing означает обработку событий по мере их появления.
Например, система группирует платежи по пользователям и почти в реальном времени ищет подозрительные комбинации.
Для таких сценариев используются специализированные Streaming Frameworks и Event Platforms.
Event Sourcing
Event Sourcing — более специфический архитектурный подход, при котором события являются главным источником истории изменения состояния.
Вместо хранения только текущего баланса система сохраняет AccountOpened, MoneyDeposited и MoneyWithdrawn.
Текущее состояние можно восстановить повторным применением событий.
Event-driven Architecture и Event Sourcing
Эти понятия нельзя считать синонимами.
Система может публиковать события через RabbitMQ и при этом хранить обычное текущее состояние в PostgreSQL.
Это Event-driven Architecture, но не обязательно Event Sourcing.
CQRS
CQRS разделяет модели изменения и чтения данных.
Команда изменяет основное состояние, а события обновляют отдельные Read Models.
Так можно оптимизировать чтение независимо от транзакционной модели записи.
Event-driven и CQRS
Событийная архитектура часто используется вместе с CQRS, но это независимые паттерны.
CQRS можно реализовать без полноценного Event Sourcing, а события использовать только для синхронизации Read Model.
Eventually Consistent
Во многих Event-driven системах разные компоненты не обновляются мгновенно.
Order Service уже создал заказ, но аналитическая система увидит его через несколько секунд.
Такое состояние называют Eventual Consistency.
Когда Eventual Consistency допустима
Для email, аналитики или рекомендаций небольшая задержка обычно приемлема.
Для списания денег или проверки последнего товара требования могут быть значительно строже.
Поэтому разные части одной системы могут использовать разные модели согласованности.
Read Model
Consumer может строить собственное представление данных из событий.
Например, Analytics Service получает OrderCreated и OrderCancelled и поддерживает отдельную таблицу ежедневных продаж.
Так ему не нужно выполнять сложные JOIN по production-базе заказов.
Materialized View
Материализованное представление может формироваться из потока событий и хранить заранее рассчитанный результат.
Например, сумма покупок каждого клиента обновляется после OrderPaid.
Это ускоряет чтение ценой дополнительной логики синхронизации.
Observability
Событийно-ориентированные системы сложнее наблюдать, чем простой синхронный Backend.
Один пользовательский запрос может запустить десятки независимых обработчиков.
Поэтому нужны Logs, Metrics и Distributed Tracing.
Correlation ID
Correlation ID связывает события одного бизнес-процесса.
Например, order_id или trace_id передается через OrderCreated, PaymentCompleted и ShipmentCreated.
Это помогает восстановить цепочку действий при расследовании ошибки.
OpenTelemetry
OpenTelemetry позволяет передавать Trace Context через сообщения.
Так Distributed Trace показывает не только HTTP Request, но и асинхронные этапы обработки.
Инженер может увидеть, что заказ создан быстро, но Warehouse Consumer обработал событие только через 20 секунд.
Метрики Event-driven систем
| Метрика | Что показывает |
|---|---|
| Event Rate | Количество событий в единицу времени |
| Consumer Lag | Отставание обработчиков |
| Processing Latency | Время обработки события |
| Error Rate | Количество ошибок Consumers |
| Retry Rate | Частоту повторной обработки |
| DLQ Size | Количество необработанных проблемных сообщений |
Безопасность событийной архитектуры
Broker является важным инфраструктурным компонентом и может содержать чувствительные бизнес-данные.
- ограничивать сетевой доступ;
- аутентифицировать Producers и Consumers;
- разграничивать доступ к Topics и Queues;
- шифровать соединения;
- не передавать лишние персональные данные;
- защищать Credentials;
- контролировать административные операции.
Минимизация Event Payload
Не следует публиковать весь объект клиента, если Consumer нужен только customer_id.
Чем больше Payload, тем сильнее связаны системы и тем выше риск утечки чувствительных данных.
В событии желательно передавать только действительно необходимые атрибуты.
Хранение персональных данных
Если Broker сохраняет события согласно Retention, персональные данные могут оставаться в инфраструктуре долгое время.
Поэтому требования к срокам хранения нужно учитывать на этапе проектирования Event Schema.
Преимущества Event-driven Architecture
- слабая связанность сервисов;
- асинхронная обработка;
- независимое масштабирование Consumers;
- буферизация всплесков нагрузки;
- легкое подключение новых подписчиков;
- удобство интеграций;
- поддержка потоковой аналитики;
- возможность строить реактивные бизнес-процессы.
Недостатки Event-driven Architecture
- сложнее отлаживать цепочку операций;
- нужно учитывать Duplicate Events;
- возможны задержки и изменение порядка сообщений;
- сложнее обеспечивать распределенную согласованность;
- нужны Broker и дополнительная инфраструктура;
- Event Schema требует версионирования;
- увеличиваются требования к Observability.
Типичные ошибки
- Превращать каждый вызов метода в отдельное событие.
- Использовать событие там, где нужен обычный синхронный Request.
- Не делать Consumers идемпотентными.
- Не добавлять Event ID и Timestamp.
- Менять Event Schema без совместимости.
- Не использовать DLQ для Poison Messages.
- Пытаться получить одну ACID Transaction между независимыми сервисами без специальной архитектуры.
- Не контролировать Consumer Lag.
- Передавать слишком большой Payload.
- Не иметь Distributed Tracing.
Как внедрять событийно-ориентированную архитектуру
Шаг 1. Выделить бизнес-события
Событие должно обозначать значимый факт предметной области: OrderCreated, PaymentReceived, ContractSigned.
Шаг 2. Определить Producers и Consumers
Нужно понимать, кто является владельцем факта и какие компоненты должны на него реагировать.
Шаг 3. Спроектировать Event Schema
Добавьте Event ID, тип события, Timestamp, идентификаторы сущностей и необходимые данные.
Шаг 4. Выбрать Broker
RabbitMQ удобен для очередей и маршрутизации, Kafka — для потоков, большого Throughput и Replay. Выбор зависит от задачи.
Шаг 5. Добавить идемпотентность
Consumer должен безопасно переживать Duplicate Delivery.
Шаг 6. Продумать Retry и DLQ
Временные и постоянные ошибки должны обрабатываться по-разному.
Шаг 7. Решить вопрос согласованности
Для Database и Broker часто применяют Transactional Outbox, а для распределенных процессов — Saga.
Шаг 8. Настроить Observability
Нужно видеть Event Rate, Lag, Processing Latency, ошибки и полный Trace бизнес-операции.
Практический пример
Интернет-магазин раньше обрабатывал оформление заказа синхронно.
Order Service создавал заказ, вызывал склад, платежи, CRM, email и аналитику. Если хотя бы один внешний сервис отвечал медленно, пользователь долго ждал.
После перехода к Event-driven Architecture Order Service сохраняет заказ в PostgreSQL и через Transactional Outbox публикует OrderCreated.
Inventory Consumer резервирует товар. Analytics Consumer обновляет показатели. Email Consumer отправляет подтверждение.
Payment Service выполняет свою операцию и публикует PaymentCompleted.
Если Email Service временно недоступен, сообщение остается в Broker и обрабатывается после восстановления.
Consumers используют Event ID и не выполняют бизнес-операцию повторно при Duplicate.
Ошибочные сообщения после ограниченного Retry попадают в DLQ.
OpenTelemetry связывает события одного заказа по Trace ID, а Prometheus отслеживает Consumer Lag.
В результате новые интеграции можно подключать к событиям без добавления прямых зависимостей в Order Service.
Событийно-ориентированная архитектура для бизнеса
Event-driven Architecture особенно полезна бизнесу, когда количество систем и интеграций становится большим.
Например, событие о продаже может одновременно использоваться складом, CRM, программой лояльности, BI и сервисом уведомлений.
Без событийной модели основной сервис пришлось бы напрямую интегрировать со всеми системами.
При этом архитектура увеличивает техническую сложность, поэтому для небольшого приложения ее преимущества могут не оправдать стоимость эксплуатации.
Когда стоит использовать Event-driven Architecture
- много независимых сервисов реагируют на одни и те же изменения;
- операции можно выполнять асинхронно;
- нужно уменьшить связанность микросервисов;
- есть всплески нагрузки;
- требуется потоковая аналитика;
- новые Consumers должны подключаться без изменения Producer;
- используются Kafka, RabbitMQ или другие брокеры;
- строится распределенная система.
Когда событийная архитектура может быть избыточной
Для небольшого монолитного CRUD-приложения с одной Database и несколькими простыми операциями события могут добавить ненужную сложность.
Если операция требует немедленного ответа от конкретного сервиса, обычный REST или gRPC вызов может быть понятнее.
Архитектуру следует выбирать исходя из реальных требований, а не использовать Event-driven подход только потому, что он популярен в микросервисах.
Связанные термины
| Термин | Связь с событийно-ориентированной архитектурой |
|---|---|
| Event | Основная единица взаимодействия |
| Producer | Публикует события |
| Consumer | Обрабатывает события |
| Message Broker | Передает сообщения между компонентами |
| Apache Kafka | Платформа потоковой передачи событий |
| RabbitMQ | Брокер сообщений для очередей и маршрутизации |
| Transactional Outbox | Согласует Database Transaction и публикацию события |
| Saga | Управляет распределенными бизнес-процессами |
| Event Sourcing | Использует события как историю состояния |
| CQRS | Может использовать события для обновления Read Model |
| CDC | Преобразует изменения базы в события |
| OpenTelemetry | Помогает трассировать асинхронные цепочки |
Краткий итог
Событийно-ориентированная архитектура — подход, при котором компоненты системы взаимодействуют через события о произошедших изменениях. Producer публикует Event, Broker передает его, а один или несколько Consumers независимо выполняют необходимые действия.
Подход уменьшает связанность сервисов, упрощает асинхронную обработку и позволяет независимо масштабировать отдельные компоненты. Он особенно полезен для микросервисов, интеграций, Data Pipeline и систем с большим количеством реакций на одни и те же бизнес-события.
При этом Event-driven Architecture требует продуманной идемпотентности, Event Schema, Retry, DLQ, контроля порядка и распределенной согласованности. Для production особенно важны Transactional Outbox, Observability и понятные границы между синхронными командами и асинхронными событиями.