День 1636. #DDD
Использование Доменных Событий для Построения Слабосвязанных Систем. Начало
В программной инженерии «связанность» означает, насколько разные части программной системы зависят друг от друга. Если они тесно связаны, изменения в одной части могут повлиять на другие. Если слабо, изменения в одной части не вызовут больших проблем в остальной системе. Доменные события — это тактический шаблон DDD, который можно использовать для создания слабосвязанных систем.
Событие представляет собой произошедший в домене факт. Другие компоненты в системе могут подписаться на это событие и соответствующим образом его обработать. События домена позволяют явно выражать побочные эффекты и обеспечивают лучшее разделение проблем в домене. Это идеальный способ вызвать побочные эффекты для нескольких агрегатов внутри домена.
Вы несёте ответственность за то, чтобы публикация события предметной области была транзакционной (об этом позже).
Особенности
- Неизменяемость — доменные события являются фактами и должны быть неизменными.
- Толстые и Тонкие события — сколько информации вам нужно передавать?
- Используйте прошедшее время для именования событий.
Доменные события и интеграционные события
Семантически это одно и то же: представление чего-то, что произошло в прошлом. Однако намерения у них разные.
События домена:
- публикуются и используется в пределах одного домена;
- отправляются с помощью шины сообщений в памяти;
- могут обрабатываться синхронно или асинхронно.
Интеграционные события:
- потребляются другими подсистемами (микросервисы, ограниченные контексты);
- отправляются брокером сообщений через очередь;
- обрабатываются только асинхронно.
Доменные события можно использовать для создания интеграционных событий, выходящих за пределы домена.
Реализация
Реализовать доменные события можно через создание абстракции IDomainEvent и реализацию INotification в MediatR. Так вы можете использовать поддержку публикации-подписки в MediatR для публикации уведомления одному или нескольким обработчикам.
using MediatR;
public interface IDomainEvent : INotification
{
}
public class CourseCompleted : IDomainEvent
{
public Guid CourseId { get; init; }
}
Вызов
Только сущности могут вызывать доменные события, поэтому создадим базовый класс Entity, сделав метод RaiseDomainEvent защищённым. Доменные события хранятся во внутренней коллекции, чтобы никто не мог получить к ним доступ. GetDomainEvents предназначен для получения моментального снимка коллекции, ClearDomainEvents — для очистки.
public abstract class Entity : IEntity
{
private readonly List<IDomainEvent>
_events = new();
public IReadOnlyList<IDomainEvent>
GetDomainEvents() => _events.ToList();
public void ClearDomainEvents()
=> _events.Clear();
protected void RaiseDomainEvent(
IDomainEvent domainEvent)
=> _events.Add(domainEvent);
}
Теперь сущности могут наследовать от Entity и вызывать доменные события:
public class Course : Entity
{
public Guid Id { get; private set; }
public CourseStatus Status { get; private set; }
public DateTime? Completed { get; private set; }
public void Complete()
{
Status = CourseStatus.Completed;
Completed = DateTime.UtcNow;
RaiseDomainEvent(
new CourseCompleted {
CourseId = this.Id
});
}
}
Окончание следует…
Источник: https://www.milanjovanovic.tech/blog/using-domain-events-to-build-loosely-coupled-systems