В мире Domain-Driven Design (DDD) и сложных бизнес-приложений Domain Events (события предметной области) являются мощным паттерном, который декоплирует логику и делает систему более гибкой и реактивной. Если говорить просто, Domain Event — это факт, что что-то значимое произошло в предметной области вашего приложения. Например, "ЗаказПодтвержден", "ПользовательЗарегистрирован", "СчетОплачен". Это не техническое событие вроде "БазаДанныхОбновлена", а событие на языке бизнеса. Реализация этого паттерна с нуля требует понимания нескольких ключевых принципов и шагов.
Первым шагом является идентификация событий. Это происходит во время тесного collaboration с доменными экспертами. Вы должны задавать вопросы: "Что является важным изменением состояния в вашем бизнес-процессе, о котором должны узнать другие части системы?" Событие всегда именуется в прошедшем времени, так как оно фиксирует уже свершившийся факт. В коде событие — это простой иммутабельный класс (или record в C#), содержащий все данные, релевантные на момент его возникновения. Например, класс `OrderConfirmedEvent` может содержать `OrderId`, `ConfirmationDate` и `CustomerId`.
Второй шаг — определение места возникновения событий. События генерируются внутри агрегатов (Aggregate Roots) — ключевых сущностей, которые контролируют инварианты. Событие — это часть агрегата, но не его прямое состояние. Стандартный подход — добавить в базовый класс агрегата приватную коллекцию (`private List _domainEvents`) для накопления событий, произошедших в ходе одной бизнес-операции. Методы агрегата, изменяющие его состояние, после успешной валидации и применения изменений создают и добавляют событие в эту коллекцию. Например, метод `Confirm()` в агрегате `Order` после установки статуса в `Confirmed` создает экземпляр `OrderConfirmedEvent` и добавляет его в список.
Третий, критически важный шаг — диспетчеризация событий. После того как агрегат сохраняется (например, через репозиторий в базу данных), накопленные события должны быть опубликованы для всех заинтересованных обработчиков (Event Handlers). Здесь возникает архитектурный выбор. Самая простая реализация "с нуля" — это паттерн Mediator в памяти. Вы создаете простой класс `DomainEventDispatcher`, который хранит регистрацию обработчиков для каждого типа события. После сохранения агрегата вы вызываете `DispatchEventsForAggregate(aggregateId)`, который извлекает события из агрегата (через метод `GetAndClearDomainEvents()`) и передает каждый обработчику. Важно: диспетчеризация должна происходить *после* успешного сохранения агрегата, чтобы гарантировать, что событие соответствует персистентному состоянию.
Четвертый шаг — обработка событий. Обработчики — это классы, реализующие интерфейс типа `IHandle`. Их задача — выполнять побочные эффекты в ответ на событие: отправить email уведомление, обновить read-модель для отчетов, инициировать следующий шаг бизнес-процесса или даже вызвать внешний API. Ключевой принцип: обработчики не должны влиять на исходную бизнес-логику агрегата и не должны бросать исключения, которые откатывают основную транзакцию (если только это не критически важно). Для фоновых задач их лучше помещать в очередь.
Пятый шаг — переход к надежной, асинхронной обработке. Наивная реализация в памяти не устойчива к сбоям. Промышленный подход предполагает сохранение событий в той же транзакции, что и агрегат (паттерн "Outbox" или "Transaction log"). События записываются в таблицу в той же БД, а затем фоновый процесс (например, с помощью библиотек типа MassTransit, NServiceBus или собственного воркера) гарантированно доставляет их обработчикам. Это обеспечивает consistency и надежность.
Реализация Domain Events с нуля — это инвестиция в архитектуру. Она позволяет строить системы, где компоненты слабо связаны, бизнес-логика ясна, а новые функции (в виде обработчиков) можно добавлять, минимально затрагивая существующий код. Начав с простого диспетчера в памяти, вы закладываете фундамент для будущего масштабирования до распределенной, событийно-ориентированной архитектуры, где события становятся кровеносной системой всего приложения.
Как Domain Events с нуля
Пошаговое руководство по реализации паттерна Domain Events (События предметной области) с нуля в рамках Domain-Driven Design: от идентификации и создания до диспетчеризации и надежной обработки.
255
2
Комментарии (6)