Работа с событиями
В современных системах, ориентированных на лучшие практики архитектуры, такие как DDD (Domain-Driven Design) и Clean Architecture, события являются фундаментальными механизмами для моделирования изменений состояния и асинхронного взаимодействия между компонентами или системами. Событие представляет собой то, что уже произошло в домене приложения и может быть интересно другим частям системы или внешним сервисам.
Доменные События
Доменные события представляют важные факты, произошедшие в контексте приложения. Они создаются и потребляются внутри системы, позволяя изменениям состояния распространяться разнесённым образом, то есть объекты не должны знать друг о друге напрямую.
Когда использовать Доменные События?
- Каждый раз, когда операция вызывает значительное изменение в домене и необходимо уведомить или запустить другие части системы.
- Для поддержания согласованности модели домена, позволяя запускать различные процессы без создания прямых зависимостей между модулями или сервисами.
В Lino создание нового доменного события просто. Вы можете выполнить:
lino event new
Мастер CLI запросит:
- Сервис – Сервис, в котором будет создано событие.
- Модуль – Модуль, в котором будет создано событие (только для модульных сервисов).
- Сущность – Сущность, к которой будет создано/ассоциировано событие.
- Тип события – Доменные события или интеграционные события.
- Название события – Название, используемое в домене, ассоциированное с сущностью.
Пример
Создавая доменное событие UserCreated, связанное с сущностью User,
система автоматически создаёт событие с названием UserCreatedDomainEvent.
Это название ясно показывает любой части системы, которая потребляет событие, что создание пользователя уже завершено.
Структура, сгенерированная Lino:
MyApp/
└── src/
└── Services/
└── MyService/
└── Domain/
├── MyApp.MyService.Domain.csproj
└── Aggregates/
└── Users/
├── User.cs
├── Errors/
├── Events/
│ └── UserCreatedDomainEvent.cs
├── Repositories/
└── Resources/
Обработчики Доменных Событий
Domain Event Handler — это класс, отвечающий за реакцию на Доменные События, выполняя действия, связанные с внутренним состоянием приложения, всегда в рамках одной и той же транзакции.
Основная цель этих обработчиков — поддерживать систему связной и разнесённой, позволяя применять дополнительные правила без перегрузки основной логики сущности или агрегата.
Например, после создания User может потребоваться обновить статистику, создать внутренний лог или уведомить другой агрегат.
Эти действия имеют смысл внутри домена и могут выполняться синхронно, обеспечивая мгновенную согласованность.
Однако операции, зависящие от внешних ресурсов — например, отправка электронной почты или вызовы сторонних API — не должны выполняться напрямую из доменных событий, так как это привяжет транзакцию к медленным или нестабильным задачам. В таких случаях домен может сгенерировать интеграционное событие (зарегистрированное в Outbox), которое будет обработано позже асинхронно и устойчиво.
Основные характеристики:
- Реагирует на событие, но никогда его не изменяет.
- Выполняет только внутрь-процессные операции, связанные с согласованностью домена.
- Гарантирует выполнение всего в рамках одной транзакции.
Чтобы создать новый обработчик доменных событий, просто выполните команду:
lino event-handler new
CLI-мастер запросит:
- Сервис – Сервис, в котором будет создан обработчик события.
- Модуль – Модуль, в котором будет создан обработчик события (только для модульных сервисов).
- Сущность – Сущность, в которой будет создан обработчик события.
- Тип события – Доменные или интеграционные события.
- Событие – Событие, которое будет обработано.
- Имя обработчика события – Имя, которое будет связано с сущностью и доменным событием.
Пример
Создавая обработчик доменного события UserCreated, связанный с сущностью User и событием UserCreatedDomainEvent, система автоматически создаёт обработчик события с именем UserCreatedDomainEventHandler.
Структура, сгенерированная Lino:
MyApp/
└── src/
└── Services/
└── MyService/
└── Application/
├── MyApp.MyService.Application.csproj
└── UseCases/
└── Users/
├── Commands/
├── EventHandlers/
│ └── Domain/
│ └── UserCreatedDomainEventHandler.cs
├── Logging/
├── Queries/
└── Resources/
События интеграции
События интеграции — это сообщения, которые указывают на то, что произошло что-то важное, и которые необходимо передавать внешним системам или другим микросервисам.
В отличие от событий домена, здесь цель — коммуникация между системами и синхронизация состояний.
Когда создавать событие интеграции:
- Когда изменение в вашей системе должно отражаться в другой системе.
- Когда ваш микросервис должен публиковать изменения, чтобы другие микросервисы могли на них реагировать.
Основные различия между Domain Events и Integration Events:
| Аспект | Событие домена | Событие интеграции |
|---|---|---|
| Целевая аудитория | Внутренняя | Внешняя |
| Связность | Низкая (внутренняя) | Необходима (между системами) |
| Время обработки | Мгновенное | Может быть асинхронным, с гарантией доставки |
| Необходимость хранения | Не обязательно | Да (для надежности и устойчивости) |
В Lino создать новое событие интеграции просто. Вы можете выполнить:
lino event new
CLI-мастер запросит:
- Сервис – Сервис, в котором будет создано событие.
- Модуль – Модуль, в котором будет создано событие (только для модульных сервисов).
- Сущность – Сущность, в которой будет создано/ассоциировано событие.
- Тип события – Событие домена или событие интеграции.
- Название события – Имя, используемое для события интеграции, связанное с сущностью.
Пример
Создавая событие интеграции UserCreated, связанное с сущностью User, система автоматически создаёт событие с именем UserCreatedIntegrationEvent.
Это имя ясно указывает любой части системы, которая потребляет событие, что действие по созданию пользователя уже выполнено.
Структура, созданная Lino:
MyApp/
└── src/
└── Services/
└── MyService/
└── IntegrationEvents/
├── MyApp.MyService.IntegrationEvents.csproj
└── Users/
└── UserCreatedIntegrationEvent.cs
Обработчики Интеграционных Событий
Integration Event Handler — это класс, который отвечает за потребление Интеграционного события, обычно опубликованного другим сервисом или контекстом, и последующее выполнение конкретных действий в своей доменной области.
Эти обработчики получают события через механизмы обмена сообщениями (например, RabbitMQ, Kafka, Azure Service Bus и др.), обычно вместе с паттерном Outbox, который обеспечивает надежную доставку и асинхронную обработку.
Например, когда событие UserCreated публикуется сервисом идентификации, соответствующий обработчик в другом контексте может реагировать, отправляя приветственное письмо или вызывая внешние API.
Эти операции могут быть медленными или подверженными ошибкам, но так как они выполняются вне основной транзакции, внутреняя согласованность приложения не нарушается.
Основные характеристики:
- Реагирует на события, представляющие бизнес-факты, важные для других контекстов.
- Выполняет операции, которые могут быть медленными или внешними (например, отправка писем, вызовы API).
- Обрабатывается асинхронно и устойчиво, часто с повторными попытками и мониторингом.
- Гарантирует, что внешние ошибки не повлияют на исходную транзакцию домена.
- Облегчает интеграцию между ограниченными контекстами и распределенными системами.
Чтобы создать новый обработчик интеграционных событий, просто выполните команду:
lino event-handler
CLI-мастер запросит:
- Сервис – Сервис, в котором будет создан обработчик события.
- Модуль – Модуль, в котором будет создан обработчик события (только для модульных сервисов).
- Сущность – Сущность, в которой будет создан обработчик события.
- Тип события – Доменное событие или интеграционное событие.
- Сервис – Сервис, в котором существует событие для потребления.
- Модуль – Модуль, в котором существует событие для потребления (только для модульных сервисов).
- Сущность – Сущность, в которой существует событие для потребления.
- Событие – Событие, которое будет потреблено.
- Название обработчика события – Название, которое будет связано с сущностью и интеграционным событием.
Пример
Создавая обработчик интеграционного события SendEmailOnUserCreated, связанный с сущностью User и событием UserCreatedIntegrationEvent,
система автоматически создает обработчик события с названием SendEmailOnUserCreatedIntegrationEventHandler.
Структура, сгенерированная Lino:
MyApp/
└── src/
└── Services/
└── MyService/
└── Application/
├── MyApp.MyService.Application.csproj
└── UseCases/
└── Users/
├── Commands/
├── EventHandlers/
│ └── Integration/
│ └── SendEmailOnUserCreatedIntegrationEventHandler.cs
├── Logging/
├── Queries/
└── Resources/
