События домена: проектирование и реализация
Совет
Это содержимое является фрагментом из электронной книги, архитектуры микрослужб .NET для контейнерных приложений .NET, доступных в документации .NET или в виде бесплатного скачиваемого PDF-файла, который можно читать в автономном режиме.
События предметной области позволяют явным образом реализовывать побочные эффекты изменений в предметной области. Другими словами и согласно терминологии DDD, события предметной области используются для явной реализации побочных эффектов между несколькими агрегатами. Кроме того, итоговая согласованность между агрегатами в рамках одной предметной области обеспечивает улучшенную масштабируемость и оказывает меньшее воздействие на блокировки базы данных.
Что такое событие предметной области?
Событие — это то, что произошло в прошлом. Событие предметной области — это то, что произошло в предметной области и о чем вы хотите уведомить остальные части той же предметной области (внутрипроцессно). Обычно уведомленные части каким-то образом реагируют на события.
Важное преимущество событий предметной области в том, что побочные эффекты можно выразить явно.
Например, если вы используете Entity Framework и на какое-то событие должна быть реакция, вы вставите нужный код рядом с тем, что активирует событие. Так что правило неявно привязывается к коду, и вам нужно заглянуть в код, чтобы, будем надеяться, понять, что правило реализовано там.
С другой стороны, использование событий домена делает концепцию явной, так как существует по DomainEvent
крайней мере один DomainEventHandler
из них.
Например, в приложении eShop при создании заказа пользователь становится покупателем, поэтому создается и обрабатывается в приложении ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
eShop, поэтому OrderStartedDomainEvent
базовая концепция очевидна.
Вкратце, события предметной области помогают явно выразить правила предметной области в соответствии с единым языком, предоставляемым специалистами в предметной области. События предметной области также обеспечивают лучшее разделение задач между классами в одной предметной области.
Важно убедиться, что, как и с транзакцией в базе данных, либо все операции, относящиеся к событию предметной области, завершаются успешно, либо ни одно из них.
События предметной области похожи на события в стиле обмена сообщениями, с одним важным отличием. В реальной системе обмена сообщениями, с очередями сообщений, посредниками или служебной шиной, использующей AMQP, сообщение всегда отправляется в асинхронном режиме и передается между процессами и компьютерами. Это полезно для интеграции нескольких ограниченных контекстов, микрослужб или даже разных приложений. Однако при использовании событий домена необходимо вызвать событие из операции домена, выполняемой в настоящее время, но вам нужно, чтобы любые побочные эффекты происходили в одном домене.
События предметной области и их побочные эффекты (запускаемые впоследствии действия, управляемые обработчиками событий) должны происходить практически немедленно, обычно в рамках процесса и в одной предметной области. Таким образом, события предметной области могут быть синхронными или асинхронными. Однако события интеграции всегда должны быть асинхронными.
События предметной области и события интеграции
Семантически события предметной области и события интеграции представляют собой одно и то же: уведомления о том, что только что произошло. Но их реализации должны быть разными. События предметной области — это просто сообщения, отправляемые диспетчеру событий предметной области, который может быть реализован в виде посредника в памяти на основе контейнера IoC или любого другого метода.
С другой стороны, события интеграции предназначены для распространения зафиксированных транзакций и обновлений в дополнительные подсистемы независимо от того, являются ли они микрослужбами, ограниченными контекстами или даже внешними приложениями. Следовательно, они должны происходить только в случае успешного сохранения сущности, в противном случае дело обстоит так, будто вся операция не выполнялась.
Как уже упоминалось, события интеграции должны основываться на асинхронной связи между несколькими микрослужбами (другими ограниченными контекстами) или даже внешними системами или приложениями.
Таким образом, интерфейсу шины событий требуется некоторая инфраструктура, позволяющая осуществлять межпроцессное и распределенное взаимодействие между потенциально удаленными службами. Ее основой могут быть коммерческая служебная шина, очереди, общая база данных, используемая в качестве почтового ящика, или любая другая распределенная и (в идеальном случае) основанная на push-уведомлениях система обмена сообщениями.
События предметной области как предпочтительный способ вызова побочных эффектов между несколькими агрегатами в одной предметной области
Если для выполнения команды, относящейся к одному экземпляру агрегата, требуется запустить дополнительные правила предметной области в одном или нескольких дополнительных агрегатах, следует разработать и реализовать побочные эффекты, запускаемые событиями предметной области. Как показано на рис. 7-14, одним из наиболее важных вариантов использования событий предметной области является распространение изменений состояния между несколькими агрегатами в одной модели предметной области.
Рис. 7-14. События предметной области для обеспечения согласованности между несколькими агрегатами в одной предметной области
На рис. 7-14 показано, как с помощью событий предметной области достигается согласованность между агрегатами. Когда пользователь оформляет заказ, агрегат Order (Заказ) отправляет событие предметной области OrderStarted
. Событие предметной области OrderStarted обрабатывается агрегатом Buyer (Покупатель), который создает объект Buyer (Покупатель) в микрослужбе по работе с заказами на основе исходных сведений о пользователе из микрослужбы идентификации (со сведениями, указанными в команде CreateOrder).
Кроме того, корень агрегата может быть подписан на события предметной области, вызываемые членами его агрегатов (дочерними сущностями). Например, каждая дочерняя сущность OrderItem может вызвать событие, когда цена товара превышает определенное значение или когда количество товарных единиц слишком велико. Затем корень агрегата может получать эти события и выполнять глобальные или статистические вычисления.
Важно понимать, что эта связь на основе событий не реализуется непосредственно в агрегатах; необходимо реализовать обработчики событий домена.
Обработкой событий предметной области занимается приложение. Уровень модели предметной области должен концентрироваться только на логике предметной области — на вещах, которые будут понятны эксперту в предметной области, а не на компонентах инфраструктуры приложений, таких как обработчики и действия сохраняемости побочных эффектов с репозиториями. Таким образом, на уровне приложения находятся обработчики событий предметной области, запускающие действия при возникновении события предметной области.
События предметной области могут также использоваться для запуска любого количества действий приложения и, что более важно, должны быть открытыми, чтобы увеличивать это количество в будущем независимо от каких-либо причин. Например, при запуске процесса заказа может потребоваться опубликовать событие предметной области для распространения этих данных в другие агрегаты или даже для вызова таких действий приложения, как уведомления.
Основной момент здесь — открытое количество действий, которые будут выполняться при возникновении события домена. В конечном итоге количество действий и правил в предметной области и приложении будет увеличиваться. Сложность или количество действий побочных эффектов будет расти, но если фрагменты кода были дополнены связующим элементом (то есть созданием определенных объектов с new
), то при каждом добавлении нового действия потребуется изменять рабочий и протестированный код.
Это изменение может привести к новым ошибкам, и этот подход также противоречит принципу открытия-закрытия из SOLID. Кроме того, исходный класс, который управлял операциями, будет постоянно увеличиваться, что противоречит принципу единственной обязанности (SRP).
С другой стороны, при использовании событий предметной области можно создать детальную и несвязанную реализацию путем разделения обязанностей с помощью приведенного далее подхода.
- Отправка команды (например, CreateOrder).
- Получение команды в обработчике команд.
- Выполнение транзакции одного агрегата.
- Вызов событий предметной области для побочных эффектов (например, OrderStartedDomainEvent) (необязательно).
- Обработка событий предметной области (в рамках текущего процесса), которые будут выполнять открытое количество побочных эффектов в нескольких агрегатах или действиях приложения. Например:
- Проверка или создание покупателя или метода оплаты.
- Создание и отправка связанного события интеграции в шину событий для распространения состояний в микрослужбах или запуска внешних действий, таких как отправка сообщения покупателю.
- Обработка других побочных эффектов.
Как показано на рис. 7-15, начиная с того же события предметной области, можно обрабатывать несколько действий, связанных с другими агрегатами в предметной области, или дополнительные действия приложения, которые необходимо выполнить в микрослужбах, взаимодействующих с событиями интеграции и шиной событий.
Рис. 7-15. Обработка нескольких действий для каждой предметной области
Может существовать несколько обработчиков для одного события предметной области на прикладном уровне: один обработчик может решать согласованность между агрегатами, а другой обработчик может публиковать событие интеграции, чтобы другие микрослужбы могли отреагировать. Обработчики событий обычно находятся на уровне приложений, так как вы будете использовать объекты инфраструктуры, такие как репозитории или API приложения для поведения микрослужбы. В этом смысле обработчики событий аналогичны обработчикам команд, поэтому оба этих типа являются частью уровня приложения. Важное отличие заключается в том, что команды должны обрабатываться только один раз. Событие предметной области может не обрабатываться или обрабатываться n раз, так как его могут получать несколько получателей или обработчиков событий с разными для каждого обработчика целями.
Наличие открытого количества обработчиков для каждого события предметной области позволяет добавлять сколько угодно правил предметной области без влияния на текущий код. Например, реализация следующего бизнес-правила может сводиться к добавлению нескольких обработчиков событий (или даже всего одного):
Если общая сумма товаров, приобретенных клиентом в магазине по любому количеству заказов, превышает 6000 USD, следует применять 10-процентную скидку к каждому новому заказу и уведомлять об этом клиента по электронной почте.
Реализация событий предметной области
В C# событие предметной области представляет собой структуру или класс хранения данных, например DTO, со всеми сведениями, связанными с действием, только что произошедшим в предметной области, как показано в следующем примере.
public class OrderStartedDomainEvent : INotification
{
public string UserId { get; }
public string UserName { get; }
public int CardTypeId { get; }
public string CardNumber { get; }
public string CardSecurityNumber { get; }
public string CardHolderName { get; }
public DateTime CardExpiration { get; }
public Order Order { get; }
public OrderStartedDomainEvent(Order order, string userId, string userName,
int cardTypeId, string cardNumber,
string cardSecurityNumber, string cardHolderName,
DateTime cardExpiration)
{
Order = order;
UserId = userId;
UserName = userName;
CardTypeId = cardTypeId;
CardNumber = cardNumber;
CardSecurityNumber = cardSecurityNumber;
CardHolderName = cardHolderName;
CardExpiration = cardExpiration;
}
}
По сути, это класс, содержащий все данные, относящиеся к событию OrderStarted.
Если говорить единым языком предметной области, поскольку событием является то, что произошло в прошлом, имя класса события должно быть представлено с глаголом в прошедшем времени, например OrderStartedDomainEvent или OrderShippedDomainEvent. Вот как событие домена реализуется в микрослужбе заказа в eShop.
Как отмечалось ранее, важной особенностью событий является то, что, так как событие является тем, что произошло в прошлом, это не должно меняться. Поэтому событие должно быть неизменяемым классом. В предыдущем коде видно, что свойства доступны только для чтения. Невозможно обновить объект, значения можно задать только при создании.
Важно подчеркнуть, что, если события предметной области должны были бы обрабатываться асинхронно, с помощью очереди, которая требует сериализации и десериализации объектов событий, для свойств нужно было бы задать закрытую установку, а не режим "только для чтения", чтобы десериализатор смог назначать значения при выведении из очереди. Это не проблема в микрослужбе заказов, поскольку событие публикации/подписки предметной области реализуется синхронно с помощью MediatR.
Создание событий предметной области
Следующий вопрос в том, как инициировать событие предметной области, чтобы оно достигало соответствующих обработчиков событий. Можно использовать несколько способов.
Уди Дахан (Udi Dahan) изначально предложил (в нескольких тематических записях блога, таких как Domain Events — Take 2 (События предметной области — два важных момента)) использовать статический класс для вызова событий и управления ими. Это может включать статический класс с именем DomainEvents, который немедленно вызывает события домена при вызове, используя синтаксис, например DomainEvents.Raise(Event myEvent)
. Джимми Богард (Jimmy Bogard) написал статью (Strengthening your domain: Domain Events (Развитие предметной области: события предметной области)), где рекомендуется подобный вариант.
Однако если класс событий предметной области является статическим, он также немедленно отправляется обработчикам. Это усложняет процедуры тестирования и отладки, поскольку обработчики событий с логикой побочных эффектов выполняются сразу же после возникновения события. При тестировании и отладке необходимо сосредоточиться на том, что происходит в текущих агрегатных классах; Вы не хотите внезапно перенаправляться на другие обработчики событий для побочных эффектов, связанных с другими агрегатами или логикой приложения. Именно поэтому были усовершенствованы другие подходы, как описано в следующем разделе.
Отложенный подход к порождению и отправке событий
Вместо немедленной отправки обработчику событий рекомендуется добавить события предметной области в коллекцию, а затем отправить их непосредственно до или непосредственно после фиксации транзакции (как в случае с SaveChanges в EF). (Джимми Богард (Jimmy Bogard) описал этот подход в публикации A better domain events pattern (Лучший шаблон для событий предметной области).)
Принятие решения о моменте отправки событий предметной области (непосредственно до или непосредственно после фиксации транзакции) является важным шагом, так как оно определяет возможность включения побочных эффектов в состав одной или разных транзакций. В последнем случае потребуется иметь дело с итоговой согласованностью между несколькими агрегатами. Эта тема будет рассматриваться в следующем разделе.
Отложенный подход — это то, что использует eShop. Сначала вы добавляете события, происходящие в сущностях, в коллекцию или список событий для каждой сущности. Этот список должен входить в состав объекта сущности или, что еще лучше, в состав базового класса сущности, как показано в следующем примере базового класса сущности.
public abstract class Entity
{
//...
private List<INotification> _domainEvents;
public List<INotification> DomainEvents => _domainEvents;
public void AddDomainEvent(INotification eventItem)
{
_domainEvents = _domainEvents ?? new List<INotification>();
_domainEvents.Add(eventItem);
}
public void RemoveDomainEvent(INotification eventItem)
{
_domainEvents?.Remove(eventItem);
}
//... Additional code
}
Когда нужно вызвать событие, вы просто добавляете его в коллекцию событий из кода в любом методе сущности корня агрегата.
Следующий код, часть корня агрегата заказа в eShop, показывает пример:
var orderStartedDomainEvent = new OrderStartedDomainEvent(this, //Order object
cardTypeId, cardNumber,
cardSecurityNumber,
cardHolderName,
cardExpiration);
this.AddDomainEvent(orderStartedDomainEvent);
Обратите внимание, что единственное действие, выполняемое методом AddDomainEvent, — это добавление события в список. Пока не отправлено ни одно событие и не вызван ни один обработчик событий.
Вы хотите отправить событие позже — при фиксации транзакции в базу данных. Если вы используете Entity Framework Core, этот момент показан в методе SaveChanges в EF DbContext, как видно в следующем коде:
// EF Core DbContext
public class OrderingContext : DbContext, IUnitOfWork
{
// ...
public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
{
// Dispatch Domain Events collection.
// Choices:
// A) Right BEFORE committing data (EF SaveChanges) into the DB. This makes
// a single transaction including side effects from the domain event
// handlers that are using the same DbContext with Scope lifetime
// B) Right AFTER committing data (EF SaveChanges) into the DB. This makes
// multiple transactions. You will need to handle eventual consistency and
// compensatory actions in case of failures.
await _mediator.DispatchDomainEventsAsync(this);
// After this line runs, all the changes (from the Command Handler and Domain
// event handlers) performed through the DbContext will be committed
var result = await base.SaveChangesAsync();
}
}
Этот код позволяет отправлять события сущностей в соответствующие обработчики событий.
Общий результат заключается в том, что вы отделили повышение события домена (простое добавление в список в памяти) от отправки его в обработчик событий. Кроме того, в зависимости от типа используемого диспетчера события можно отправлять синхронно или асинхронно.
Следует иметь в виду, что здесь начинают играть важную роль транзакционные ограничения. Если единица работы и транзакция могут относиться к нескольким агрегатам (как при использовании EF Core и реляционной базы данных), этот вариант может быть успешным. Но если транзакция не может охватывать агрегаты, необходимо реализовать дополнительные шаги для обеспечения согласованности. Это еще одна причина, по которой пропуск сохраняемости не является универсальным. Он зависит от используемой системы хранения.
Одна транзакция между агрегатами и итоговая согласованность между агрегатами
Вопрос о том, нужно ли выполнять одну транзакцию в агрегатах или следует полагаться на итоговую согласованность между этими агрегатами, является противоречивым. Многие авторы статей по DDD, среди которых Эрик Эванс (Eric Evans) и Вон Вернон (Vaughn Vernon), выступают в поддержку правила о том, что одна транзакция соответствует одному агрегату, и поддерживают итоговую согласованность между агрегатами. Например, в своей книге Domain-Driven Design (Разработка на основе предметной области) Эрик Эванс пишет следующее:
"Не следует ожидать, что любое правило, которое распространяется на агрегаты, всегда будет актуальным. Применив обработку событий, пакетную обработку или другие механизмы обновления, можно устранить зависимости в установленные конкретные строки". (стр. 128)
Вон Вернон говорит следующее в эффективном агрегатном дизайне. Часть II. Создание агрегатов работает вместе:
Таким образом, если выполнение команды в одном агрегатном экземпляре требует выполнения дополнительных бизнес-правил для одной или нескольких агрегатов, используйте конечную согласованность [...] Существует практический способ поддержки конечной согласованности в модели DDD. Метод агрегата публикует событие предметной области, которое своевременно доставляется одному или нескольким асинхронным подписчикам.
Этот подход основан на применении детальных транзакций вместо транзакций, используемых в нескольких агрегатах или сущностях. Смысл в том, что во втором случае количество блокировок базы данных будет существенным в крупномасштабных приложениях, требующих высокого уровня масштабируемости. Признание того факта, что высокомасштабируемым приложениям не нужна мгновенная транзакционная согласованность между несколькими агрегатами, помогает принять концепцию итоговой согласованности. Как правило, бизнесу не требуются еле заметные изменения, и в любом случае эксперты в предметной области обязаны определить необходимость атомарных транзакций для конкретных операций. Если операция всегда нуждается в атомарных транзакциях между несколькими агрегатами, вы можете задать вопрос о том, должен ли агрегат быть больше или неправильно разработан.
Однако другие разработчики и архитекторы, такие как Джимми Богард (Jimmy Bogard), нормально относятся к тому факту, что одна транзакция используется для нескольких агрегатов, но только если эти дополнительные агрегаты связаны с побочными эффектами для одной и той же исходной команды. К примеру, в статье A better domain events pattern (Лучший шаблон событий предметной области) Богард говорит следующее.
Как правило, я хочу, чтобы побочные эффекты события домена происходили в той же логической транзакции, но не обязательно в той же области вызова события домена [...] Перед фиксацией транзакции мы отправим события в соответствующие обработчики.
Отправка событий до фиксации исходной транзакции означает, что побочные эффекты этих событий должны быть включены в ту же транзакцию. Например, при сбое метода SaveChanges EF DbContext транзакция откатит все изменения, включая результат операций побочных эффектов, реализованных связанными обработчиками событий предметной области. Это связано с тем, что область жизни DbContext по умолчанию определяется как "область действия". Таким образом, объект DbContext совместно используется для нескольких объектов репозитория, создаваемых в одной области или графе объектов. Это понятие совпадает с областью HttpRequest при разработке веб-API или приложений MVC.
В действительности правильными могут быть оба подхода (одна атомарная транзакция и итоговая согласованность). Это зависит от требований предметной области или бизнес-требований, а также от мнений экспертов в предметной области. Значение также имеет то, насколько масштабируемой должна быть служба (более детализированные транзакции оказывают меньше влияния на блокировку баз данных). И это зависит от того, сколько инвестиций вы готовы внести в код, так как в конечном итоге согласованность требует более сложного кода, чтобы обнаружить возможные несоответствия между агрегатами и необходимость реализации компенсирующих действий. Учитывайте, что если вы фиксируете изменения в исходной агрегатной и последующей отправке событий, если возникает проблема и обработчики событий не могут зафиксировать их побочные эффекты, вы будете иметь несоответствия между агрегатами.
Реализация компенсационных действий заключается в сохранении событий предметной области в дополнительных таблицах базы данных, чтобы они могли быть частью исходной транзакции. После этого в вашем распоряжении может находиться пакетный процесс, который обнаруживает несоответствия и выполняет компенсационные действия, сравнивая список событий с текущим состоянием агрегатов. Компенсационные действия являются частью сложной темы, которая потребует глубокого анализа и обсуждения вопросов с бизнес-пользователями и экспертами в предметной области.
В любом случае можно выбрать нужный подход. Но первоначальный отложенный подход — создание событий до фиксации и вытекающее отсюда использование одной транзакций — является простейшим при работе с EF Core и реляционной базой данных. Проще реализовать и действовать во многих бизнес-случаях. Это также подход, используемый в заказ микрослужбе в eShop.
Но как на самом деле эти события отправляются их соответствующим обработчикам событий? Что такое объект _mediator
в предыдущем примере? Здесь речь идет о методах и артефактах, которые можно использовать для сопоставления событий и их обработчиков событий.
Диспетчер событий предметной области: сопоставление событий и обработчиков событий
Получив возможность отправки или публикации событий, вам потребуется какой-то артефакт, который будет публиковать событие, чтобы каждый связанный обработчик мог получать его и обрабатывать побочные эффекты, возникающие из-за этого события.
Одним из вариантов является реальная система обмена сообщениями или даже шина событий, возможно, на основе служебной шины в отличие от событий в памяти. Однако в первом случае реальный обмен сообщениями был бы избыточным для обработки событий предметной области, поскольку нужно просто обработать эти события в одном процессе (то есть в пределах одного уровня предметной области и приложения).
Подписка на события предметной области
При использовании MediatR каждый обработчик событий должен использовать тип события, указанный в универсальном параметре INotificationHandler
интерфейса, как показано в следующем коде:
public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
: INotificationHandler<OrderStartedDomainEvent>
На основе связи между событием и обработчиком событий, который можно считать подпиской, артефакт MediatR может обнаруживать все обработчики событий для каждого события и запускать каждый из них.
Обработка событий предметной области
И, наконец, обработчик событий обычно реализует код уровня приложения, который использует репозитории инфраструктуры для получения необходимых дополнительных агрегатов, а также для выполнения логики предметной области с побочными эффектами. В следующем коде обработчика событий домена в eShop показан пример реализации.
public class ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler
: INotificationHandler<OrderStartedDomainEvent>
{
private readonly ILogger _logger;
private readonly IBuyerRepository _buyerRepository;
private readonly IOrderingIntegrationEventService _orderingIntegrationEventService;
public ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler(
ILogger<ValidateOrAddBuyerAggregateWhenOrderStartedDomainEventHandler> logger,
IBuyerRepository buyerRepository,
IOrderingIntegrationEventService orderingIntegrationEventService)
{
_buyerRepository = buyerRepository ?? throw new ArgumentNullException(nameof(buyerRepository));
_orderingIntegrationEventService = orderingIntegrationEventService ?? throw new ArgumentNullException(nameof(orderingIntegrationEventService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task Handle(
OrderStartedDomainEvent domainEvent, CancellationToken cancellationToken)
{
var cardTypeId = domainEvent.CardTypeId != 0 ? domainEvent.CardTypeId : 1;
var buyer = await _buyerRepository.FindAsync(domainEvent.UserId);
var buyerExisted = buyer is not null;
if (!buyerExisted)
{
buyer = new Buyer(domainEvent.UserId, domainEvent.UserName);
}
buyer.VerifyOrAddPaymentMethod(
cardTypeId,
$"Payment Method on {DateTime.UtcNow}",
domainEvent.CardNumber,
domainEvent.CardSecurityNumber,
domainEvent.CardHolderName,
domainEvent.CardExpiration,
domainEvent.Order.Id);
var buyerUpdated = buyerExisted ?
_buyerRepository.Update(buyer) :
_buyerRepository.Add(buyer);
await _buyerRepository.UnitOfWork
.SaveEntitiesAsync(cancellationToken);
var integrationEvent = new OrderStatusChangedToSubmittedIntegrationEvent(
domainEvent.Order.Id, domainEvent.Order.OrderStatus.Name, buyer.Name);
await _orderingIntegrationEventService.AddAndSaveEventAsync(integrationEvent);
OrderingApiTrace.LogOrderBuyerAndPaymentValidatedOrUpdated(
_logger, buyerUpdated.Id, domainEvent.Order.Id);
}
}
Предыдущий код обработчика событий предметной области считается кодом уровня приложения, так как он использует инфраструктуру репозиториев, как описано в следующем разделе об уровне сохраняемости инфраструктуры. Обработчики событий также могут использовать другие компоненты инфраструктуры.
События предметной области способны создавать события интеграции за пределами микрослужбы
В заключение важно отметить, что иногда может потребоваться распространить события в нескольких микрослужбах. Такое распространение считается событием интеграции и может быть опубликовано с помощью шины событий из любого конкретного обработчика событий предметной области.
Выводы по событиям предметной области
Как уже говорилось, события предметной области позволяют явно реализовать побочные эффекты изменений в предметной области. В соответствии с терминологией DDD события предметной области используются для явной реализации побочных эффектов в одном или нескольких агрегатах. Кроме того, итоговая согласованность между агрегатами в рамках одной предметной области обеспечивает улучшенную масштабируемость и оказывает меньшее воздействие на блокировки базы данных.
Эталонное приложение использует MediatR для синхронного распространения событий домена по агрегатам в рамках одной транзакции. Тем не менее вы можете также использовать реализацию AMQP, например RabbitMQ или Служебная шина Azure, для асинхронного распространения событий предметной области, используя итоговую согласованность, но, как упоминалось выше, необходимо учитывать потребность в компенсаторных действиях в случае сбоев.
Дополнительные ресурсы
Грег Янг (Greg Young). Что такое событие предметной области?
https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf#page=25Ян Стенберг (Jan Stenberg). События предметной области и итоговая согласованность
https://www.infoq.com/news/2015/09/domain-events-consistencyДжимми Богард (Jimmy Bogard). Улучшенный шаблон событий предметной области
https://lostechies.com/jimmybogard/2014/05/13/a-better-domain-events-pattern/Вон Вернон (Vaughn Vernon). Эффективное агрегатное проектирование. Часть II. Организация совместной работы агрегатов
https://dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_2.pdfДжимми Богард (Jimmy Bogard). Усиление предметной области: события предметной области
https://lostechies.com/jimmybogard/2010/04/08/strengthening-your-domain-domain-events/Уди Дахан (Udi Dahan). Создание полностью инкапсулированных моделей предметной области
https://udidahan.com/2008/02/29/how-to-create-fully-encapsulated-domain-models/Уди Дахан (Udi Dahan). События предметной области. Попытка 2
https://udidahan.com/2008/08/25/domain-events-take-2/Уди Дахан (Udi Dahan). События предметной области. Спасение
https://udidahan.com/2009/06/14/domain-events-salvation/Сезар де ла Торре (Cesar de la Torre). События домена и события интеграции в архитектуре DDD и микрослужб
https://devblogs.microsoft.com/cesardelatorre/domain-events-vs-integration-events-in-domain-driven-design-and-microservices-architectures/