Используйте события домена, чтобы зафиксировать то, что происходит в домене.
Практики, ориентированные на предметную область, обнаруживают, что они могут лучше понять проблемную область, узнавая больше о том, что происходит в проблемной области. Эти события являются событиями предметной области, которые в основном получаются в процессе извлечения знаний экспертами предметной области.
События предметной области, которые можно использовать в модели предметной области в ограниченном контексте, также можно использовать для асинхронной связи между ограниченными контекстами с использованием очередей сообщений.
1 Понимание доменных событий
События предметной области — это события, происходящие в предметной области, которые представляют интерес для экспертов предметной области.
Смоделируйте действия, происходящие в предметной области, как серию дискретных событий. Каждое событие представлено доменным объектом. События предметной области являются частью модели предметной области и представляют то, что происходит в предметной области.
Основные области применения доменных событий:
- Обеспечение согласованности данных между агрегациями
- Заменить пакетную обработку
- Реализация шаблона источника событий
- Выполнение интеграции с ограниченным контекстом
2 Реализация доменных событий
Событие предметной области представляет собой факт, который произошел, и этот факт не меняется после того, как он произошел. Поэтому события предметной области часто моделируются как объекты-значения.
Однако бывают и особые случаи: чтобы удовлетворить потребности фреймворка сериализации и десериализации, при моделировании часто идут на определенные компромиссы.
2.1 Создание доменных событий
2.1.1 наименование события
При моделировании событий предметной области мы должны называть события в соответствии с общим языком в ограниченном контексте.
Если событие генерируется командной операцией над агрегатом, событие обычно именуется в соответствии с именем метода операции. Имя события указывает на то, что метод команды на агрегате выполняется успешно. То есть наименование события должно отражать то, что произошло в прошлом.
public class AccountEnabledEvent extends AbstractAggregateEvent<Long, Account> {
public AccountEnabledEvent(Account source) {
super(source);
}
}
2.1.2 Свойства события
Атрибуты событий в основном используются для управления последующими бизнес-процессами. Конечно, будут и некоторые общие свойства.
События имеют некоторые общие свойства, такие как:
- Уникально идентифицирует
- произошлоВо время возникновения
- тип тип события
- source Источник события (только для событий, сгенерированных агрегацией)
Общие свойства можно указать с помощью интерфейса событий.
интерфейс или класс | значение |
---|---|
DomainEvent | Общий интерфейс доменных событий |
AggregateEvent | Общий интерфейс событий предметной области, публикуемый агрегатами |
AbstractDomainEvent | Класс реализации DomainEvent, поддерживает идентификатор и время создания |
AbstractAggregateEvent | AggregateEvent реализует класс, наследует дочерний элемент AbstractDomainEvent и добавляет исходный атрибут. |
Однако самое главное в мероприятии — это бизнес-атрибут. Нам необходимо рассмотреть, кто вызвал событие, это может быть агрегат, создавший событие, или другие агрегаты, участвующие в операции, или любой другой тип операционных данных.
2.1.3 метод события
События представляют собой описания фактов и сами по себе не имеют особого делового характера.
События предметной области обычно разрабатываются как неизменяемые объекты, и данные, переносимые событием, уже отражают источник события. Конструктор событий завершает инициализацию состояния и предоставляет методы получения свойств.
2.1.4 уникальный идентификатор события
Здесь необходимо отметить уникальный идентификатор события.Обычно событие неизменяемо, так почему же оно связано с концепцией уникального идентификатора?
Для событий предметной области, опубликованных из агрегации, достаточно различать разные события, используя имя события, идентификатор события, сгенерировавшего событие, и время, когда событие произошло. Однако это усложняет сравнение событий.
Для событий, опубликованных вызывающей стороной, мы моделируем события предметной области как агрегаты, и мы можем напрямую использовать уникальный идентификатор агрегата в качестве идентификатора события.
Введение уникального идентификатора события значительно упростит сравнение событий. Однако его наибольшее значение заключается в интеграции ограниченных контекстов.
Когда нам нужно публиковать события предметной области во внешнем ограниченном контексте, необходима уникальная идентификация. Чтобы обеспечить идемпотентность доставки события, на стороне отправителя мы можем сделать несколько попыток отправки, пока не будет ясно, что доставка прошла успешно; на стороне получателя, после получения события, событие необходимо повторно обнаружить, чтобы гарантировать событие Идемпотентность обработки. На этом этапе уникальный идентификатор события можно использовать в качестве основы для дедупликации события.
Уникальная идентификация событий мало влияет на моделирование предметной области, но дает большие преимущества для технической обработки. Так что управляйте им как общим свойством.
2.2 Публикация доменных событий
Как избежать связи между событиями предметной области и обработчиками?
Простой и эффективный способ — использовать шаблон Observer, который отделяет события предметной области от внешних компонентов.
2.2.1 модель публикации-подписки
Чтобы унифицировать, нам нужно определить набор интерфейсов и классов реализации для завершения публикации событий на основе шаблона наблюдателя.
Используемые интерфейсы и классы реализации следующие:
интерфейс или класс | значение |
---|---|
DomainEventPublisher | Используется для публикации доменных событий |
DomainEventHandlerRegistry | Используется для регистрации DomainEventHandler |
DomainEventBus | Расширения от DomainEventPublisher и DomainEventHandlerRegistry для публикации и управления обработчиками событий домена. |
DefaultDomainEventBus | Реализация DomainEventBus по умолчанию |
DomainEventHandler | для обработки доменных событий |
DomainEventSubscriber | Используется, чтобы определить, принимать ли события домена |
DomainEventExecutor | Используется для выполнения обработчиков событий домена. |
Используйте такой пример, какDomainEventBusTestпоказано:
public class DomainEventBusTest {
private DomainEventBus domainEventBus;
@Before
public void setUp() throws Exception {
this.domainEventBus = new DefaultDomainEventBus();
}
@After
public void tearDown() throws Exception {
this.domainEventBus = null;
}
@Test
public void publishTest(){
// 创建事件处理器
TestEventHandler eventHandler = new TestEventHandler();
// 注册事件处理器
this.domainEventBus.register(TestEvent.class, eventHandler);
// 发布事件
this.domainEventBus.publish(new TestEvent("123"));
// 检测事件处理器是够运行
Assert.assertEquals("123", eventHandler.data);
}
@Value
class TestEvent extends AbstractDomainEvent{
private String data;
}
class TestEventHandler implements DomainEventHandler<TestEvent>{
private String data;
@Override
public void handle(TestEvent event) {
this.data = event.getData();
}
}
}
После создания структуры публикации-подписки ее необходимо связать с моделью предметной области. Как модель предметной области получает издателя и как подписывается обработчик событий.
2.2.2 Публикация событий на основе ThreadLocal
Более распространенным решением является привязка DomainEventBus к контексту потока. Таким образом, пока тот же вызывающий поток может легко получить объект DomainEventBus.
Конкретные взаимодействия заключаются в следующем:
DomainEventBusHolderИспользуется для управления DomainEventBus.
public class DomainEventBusHolder {
private static final ThreadLocal<DomainEventBus> THREAD_LOCAL = new ThreadLocal<DomainEventBus>(){
@Override
protected DomainEventBus initialValue() {
return new DefaultDomainEventBus();
}
};
public static DomainEventPublisher getPubliser(){
return THREAD_LOCAL.get();
}
public static DomainEventHandlerRegistry getHandlerRegistry(){
return THREAD_LOCAL.get();
}
public static void clean(){
THREAD_LOCAL.remove();
}
}
AccountВключение использует DomainEventBusHolder непосредственно для публикации.
public class Account extends JpaAggregate {
public void enable(){
AccountEnabledEvent event = new AccountEnabledEvent(this);
DomainEventBusHolder.getPubliser().publish(event);
}
}
public class AccountEnabledEvent extends AbstractAggregateEvent<Long, Account> {
public AccountEnabledEvent(Account source) {
super(source);
}
}
AccountApplicationЗавершите регистрацию подписчика и вызов бизнес-метода.
public class AccountApplication extends AbstractApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountApplication.class);
@Autowired
private AccountRepository repository;
public void enable(Long id){
// 清理之前绑定的 Handler
DomainEventBusHolder.clean();
// 注册 EventHandler
AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler();
DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler);
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 使用 DomainEventBusHolder 直接发布事件
account.enable();
repository.save(account);
}
}
class AccountEnableEventHandler implements DomainEventHandler<AccountEnabledEvent>{
@Override
public void handle(AccountEnabledEvent event) {
LOGGER.info("handle enable event");
}
}
}
2.2.3 Публикация событий на основе кеша сущностей
Событие сначала кэшируется в сущности, а событие публикуется после успешного сохранения состояния сущности в хранилище.
Конкретные взаимодействия заключаются в следующем:
Пример кода выглядит следующим образом:
public class Account extends JpaAggregate {
public void enable(){
AccountEnabledEvent event = new AccountEnabledEvent(this);
registerEvent(event);
}
}
Accountизenableметод, вызовregisterEventЗарегистрируйтесь на мероприятия.
@MappedSuperclass
public abstract class AbstractAggregate<ID> extends AbstractEntity<ID> implements Aggregate<ID> {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAggregate.class);
@JsonIgnore
@QueryTransient
@Transient
@org.springframework.data.annotation.Transient
private final transient List<DomainEventItem> events = Lists.newArrayList();
protected void registerEvent(DomainEvent event) {
events.add(new DomainEventItem(event));
}
protected void registerEvent(Supplier<DomainEvent> eventSupplier) {
this.events.add(new DomainEventItem(eventSupplier));
}
@Override
@JsonIgnore
public List<DomainEvent> getEvents() {
return Collections.unmodifiableList(events.stream()
.map(eventSupplier -> eventSupplier.getEvent())
.collect(Collectors.toList()));
}
@Override
public void cleanEvents() {
events.clear();
}
private class DomainEventItem {
DomainEventItem(DomainEvent event) {
Preconditions.checkArgument(event != null);
this.domainEvent = event;
}
DomainEventItem(Supplier<DomainEvent> supplier) {
Preconditions.checkArgument(supplier != null);
this.domainEventSupplier = supplier;
}
private DomainEvent domainEvent;
private Supplier<DomainEvent> domainEventSupplier;
public DomainEvent getEvent() {
if (domainEvent != null) {
return domainEvent;
}
DomainEvent event = this.domainEventSupplier != null ? this.domainEventSupplier.get() : null;
domainEvent = event;
return domainEvent;
}
}
}
registerEventметод вAbstractAggregate, метод registerEvent сохраняет событие в коллекцию событий,getEventsметод получения всех событий,cleanEventsМетод очистки кэшированных событий.
Экземпляр приложения выглядит следующим образом:
@Service
public class AccountApplication extends AbstractApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountApplication.class);
@Autowired
private AccountRepository repository;
@Autowired
private DomainEventBus domainEventBus;
@PostConstruct
public void init(){
// 使用 Spring 生命周期注册事件处理器
this.domainEventBus.register(AccountEnabledEvent.class, new AccountEnableEventHandler());
}
public void enable(Long id){
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 将事件缓存在 account 中
account.enable();
repository.save(account);
List<DomainEvent> events = account.getEvents();
if (!CollectionUtils.isEmpty(events)){
// 成功持久化后,对事件进行发布
this.domainEventBus.publishAll(events);
}
}
}
class AccountEnableEventHandler implements DomainEventHandler<AccountEnabledEvent>{
@Override
public void handle(AccountEnabledEvent event) {
LOGGER.info("handle enable event");
}
}
}
AccountApplicationизinitМетод завершает регистрацию прослушивателя событий,enableМетод передает кэшированное событие после успешного сохранения сущности.DomainEventBusЭкземпляр опубликован.
2.2.4 События, опубликованные вызывающим абонентом
Обычно события предметной области генерируются агрегированными командными методами, и события публикуются после успешного выполнения командных методов. Иногда доменные события генерируются не командными методами в агрегатах, а запросами пользователей.
На данный момент нам нужно смоделировать события предметной области как совокупность и иметь собственный репозиторий. Однако, поскольку события предметной области представляют то, что произошло в прошлом, библиотека ресурсов выполняет только операции добавления и не может изменять или удалять события.
Например, опубликуйте событие клика пользователя.
@Entity
@Data
public class ClickAction extends JpaAggregate implements DomainEvent {
@Setter(AccessLevel.PRIVATE)
private Long userId;
@Setter(AccessLevel.PRIVATE)
private String menuId;
public ClickAction(Long userId, String menuId){
Preconditions.checkArgument(userId != null);
Preconditions.checkArgument(StringUtils.isNotEmpty(menuId));
setUserId(userId);
setMenuId(menuId);
}
@Override
public String id() {
return String.valueOf(getId());
}
@Override
public Date occurredOn() {
return getCreateTime();
}
}
ClickActionунаследовано отJpaAggregateвыполнитьDomainEventинтерфейс и переопределить методы id и existsOn.
@Service
public class ClickActionApplication extends AbstractApplication {
@Autowired
private ClickActionRepository repository;
@Autowired
private DomainEventBus domainEventBus;
public void clickMenu(Long id, String menuId){
ClickAction clickAction = new ClickAction(id, menuId);
clickAction.prePersist();
this.repository.save(clickAction);
domainEventBus.publish(clickAction);
}
}
ClickActionApplicationПосле успешного сохранения ClickAction используйте DomainEventBus для публикации события.
2.3 Подписка на события домена
Какой компонент регистрирует подписчиков на события предметной области? Большинство запросов выполняются службой приложений, а иногда она может быть зарегистрирована службой домена.
Поскольку служба приложения является непосредственным клиентом модели предметной области, это идеальное место для регистрации подписчиков на события предметной области, то есть до того, как служба приложения вызовет метод предметной области, подписка на событие завершена.
Подписка на основе ThreadLocal:
public void enable(Long id){
// 清理之前绑定的 Handler
DomainEventBusHolder.clean();
// 注册 EventHandler
AccountEnableEventHandler enableEventHandler = new AccountEnableEventHandler();
DomainEventBusHolder.getHandlerRegistry().register(AccountEnabledEvent.class, enableEventHandler);
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 使用 DomainEventBusHolder 直接发布事件
account.enable();
repository.save(account);
}
}
Чтобы подписаться на основе кеша сущностей:
@PostConstruct
public void init(){
// 使用 Spring 生命周期注册事件处理器
this.domainEventBus.register(AccountEnabledEvent.class, new AccountEnableEventHandler());
}
public void enable(Long id){
Optional<Account> accountOptional = repository.getById(id);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
// enable 将事件缓存在 account 中
account.enable();
repository.save(account);
List<DomainEvent> events = account.getEvents();
if (!CollectionUtils.isEmpty(events)){
// 成功持久化后,对事件进行发布
this.domainEventBus.publishAll(events);
}
}
}
2.4 Обработка доменных событий
Закончив публикацию событий, давайте взглянем на обработку событий.
2.4.1 Обеспечение согласованности данных между агрегациями
Обычно мы используем события предметной области для поддержания согласованности модели. В агрегатном моделировании существует принцип, согласно которому в транзакции может быть изменен только один агрегат, а полученные изменения должны выполняться в отдельной транзакции.
В этом случае с распространением транзакций нужно обращаться осторожно.
Служба приложения контролирует транзакцию. Не изменяйте другой экземпляр агрегата во время уведомления о событии, так как это нарушит один из главных принципов агрегатов: в транзакции можно изменить только один агрегат.
Для простых сценариев мы можем изолировать совокупные модификации, используя специальную стратегию изоляции транзакций. Конкретный процесс выглядит следующим образом:
Однако лучшим решением является использование асинхронной обработки. И каждый определитель изменяет дополнительные агрегатные экземпляры в своих отдельных транзакциях.
Подписчикам событий не следует выполнять методы команд на другом агрегате, так как это нарушит принцип «изменить только один экземпляр агрегата в рамках одной транзакции». Возможная согласованность между всеми агрегатными экземплярами должна обрабатываться асинхронно.
Дополнительные сведения см. в разделе Асинхронная обработка событий домена.
2.4.2 Заменить пакетную обработку
Пакетные процессы часто требуют сложных запросов и поддержки огромных транзакций. Если система обрабатывает события предметной области сразу после их получения, бизнес-требования не только выполняются быстрее, но и исключаются пакетные операции.
В непиковые периоды работы системы пакетная обработка обычно используется для выполнения некоторого обслуживания системы, такого как удаление данных с истекшим сроком действия, создание новых объектов, уведомление пользователей, обновление статистики и т. д. Эти пакеты часто требуют сложных запросов и поддержки огромных транзакций.
Если мы прослушиваем события домена в системе, система обрабатывает их сразу же, как только мы получаем события домена. Таким образом, первоначальный процесс централизованной пакетной обработки делится на множество небольших обрабатывающих единиц, бизнес-потребности могут удовлетворяться быстрее, а пользователи могут своевременно переходить к следующему шагу.
2.4.3 Реализация шаблона источника событий
Поддержка хранилища событий для всех событий предметной области в одном ограниченном контексте дает множество преимуществ.
Хранение событий может:
- Используйте хранилище событий в качестве очереди сообщений, а затем публикуйте события предметной области с помощью средства обработки сообщений.
- Используйте хранилище событий для уведомлений о событиях на основе отдыха.
- Проверьте историю результатов, полученных методом именования моделей.
- Используйте хранилище событий для бизнес-прогнозирования и анализа.
- Используйте события для перестроения агрегатных экземпляров.
- Выполните агрегированную операцию отмены.
Хранение событий — относительно большая тема, и она будет объяснена в отдельной главе.
2.4.4 Выполнение интеграции с ограниченным контекстом
Интеграция с ограниченным контекстом, основанная на событиях предметной области, в основном состоит из двух режимов: очередь сообщений и событие REST.
Здесь основное внимание уделяется контекстной интеграции на основе очередей сообщений.
Применяя системы сообщений в разных контекстах, мы должны гарантировать возможную согласованность. В этом случае нам необходимо сохранить возможную согласованность как минимум между двумя хранилищами: хранилищем, используемым моделью предметной области, и постоянным хранилищем, используемым очередью сообщений. Мы должны убедиться, что при сохранении модели предметной области события для нее также были успешно опубликованы. Если они не синхронизированы, модель может быть в неправильном состоянии.
В общем, есть три пути:
- Модели доменов и сообщения совместно используют постоянное хранилище. В этом случае фиксация модели и события выполняется в одной транзакции, что гарантирует согласованность обоих.
- Модели предметной области и сообщения контролируются глобальными транзакциями. В этом случае постоянное хранилище для моделей и сообщений можно разделить, но производительность системы снизится.
- В постоянном хранилище домена создается специальная область хранения для хранения событий (то есть хранилище событий), так что хранение доменов и событий осуществляется в локальной транзакции. Затем события асинхронно отправляются в очередь сообщений через фоновую службу.
В общем, третий — более элегантное решение.
Когда требования согласованности невысоки, события можно отправлять непосредственно в очередь сообщений через подписчика событий домена. Конкретный процесс выглядит следующим образом:
Когда требования к согласованности высоки, события необходимо сначала сохранить, а затем загрузить и распределить в очередь сообщений через фоновый поток. Конкретный процесс выглядит следующим образом:
2.5 Асинхронная обработка доменных событий
События домена можно координировать с асинхронными рабочими процессами, включая асинхронную связь между ограниченными контекстами с использованием очередей сообщений. Конечно, в том же ограниченном контексте можно запустить и асинхронную обработку.
Как издатель событий вы не должны беспокоиться об асинхронной обработке. Обработка исключений определяется исполнителем события.
DomainEventExecutorОбеспечить поддержку асинхронной обработке.
DomainEventExecutor eventExecutor =
new ExecutorBasedDomainEventExecutor("EventHandler", 1, 100);
this.domainEventBus.register(AccountEnabledEvent.class,
eventExecutor,
new AccountEnableEventHandler());
Асинхронная обработка означает отказ от ACID-природы транзакций базы данных в пользу конечной согласованности.
2.6 Внутренние и внешние события
При использовании доменных событий необходимо различать события, чтобы избежать проблем с технической реализацией.
Крайне важно понимать разницу между внутренними и внешними событиями.
- Внутренние события — это события в модели предметной области, которые не используются в ограниченных контекстах.
- Внешние события — это события, публикуемые извне и совместно используемые несколькими ограниченными контекстами.
Как правило, в типичном бизнес-варианте может быть много внутренних событий и только одно или два внешних события.
2.6.1 внутреннее событие
Внутренние события существуют внутри ограниченного контекста, защищенного границами ограниченного контекста.
Внутренние события ограничены границами одного ограниченного контекста, поэтому они могут напрямую ссылаться на объекты предметной области.
public interface AggregateEvent<ID, A extends Aggregate<ID>> extends DomainEvent{
A source();
default A getSource(){
return source();
}
}
НапримерAggregateEventсерединаsourceУказывает на агрегат, опубликовавший это событие.
public class LikeSubmittedEvent extends AbstractAggregateEvent<Long, Like> {
public LikeSubmittedEvent(Like source) {
super(source);
}
public LikeSubmittedEvent(String id, Like source) {
super(id, source);
}
}
LikeSubmittedEventКласс напрямую ссылается на агрегат Like.
2.6.2 внешнее событие
Внешние события существуют между ограниченными контекстами и совместно используются несколькими контекстами.
В общем, внешние события существуют только как носители информации. Часто используется плоская структура и выставляются все свойства.
@Data
public class SubmittedEvent {
private Owner owner;
private Target target;
}
SubmittedEventЭто плоская структура, в основном инкапсуляция данных.
Поскольку внешние события используются несколькими контекстами, важно управлять версиями, чтобы избежать критических изменений, влияющих на их службы.
3. Реализация шаблона событий предметной области
События домена — это общий шаблон, суть которого заключается в добавлении концепций предметной области в шаблон публикации-подписки.
3.1 Шаблон «публикация-подписка», который инкапсулирует события предметной области
Публикация-подписка — это относительно зрелый шаблон проектирования с высокой универсальностью. Поэтому рекомендуется инкапсулировать в соответствии с требованиями домена.
например, непосредственное использованиеgeekhalo-dddсопутствующие модули.
Определить события домена:
@Value
public class LikeCancelledEvent extends AbstractAggregateEvent<Long, Like> {
public LikeCancelledEvent(Like source) {
super(source);
}
}
Подпишитесь на события домена:
this.domainEventBus.register(LikeCancelledEvent.class, likeCancelledEvent->{
CanceledEvent canceledEvent = new CanceledEvent();
canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner());
canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(canceledEvent);
});
Выполнять события домена асинхронно:
DomainEventExecutor eventExecutor =
new ExecutorBasedDomainEventExecutor("LikeEventHandler", 1, 100);
this.domainEventBus.register(LikeCancelledEvent.class,
eventExecutor,
likeCancelledEvent->{
CanceledEvent canceledEvent = new CanceledEvent();
canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner());
canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(canceledEvent);
});
3.2 Шина памяти обрабатывает внутренние события, а очередь сообщений обрабатывает внешние события.
Шина памяти проста и эффективна, поддерживает как синхронные, так и асинхронные схемы обработки.Она больше подходит для обработки сложных внутренних событий.Несмотря на сложность очереди сообщений, она хороша для решения задач межсервисного взаимодействия и подходит для обработки внешних Мероприятия.
3.3 Использование событий домена Entity Cache
Теоретически события должны публиковаться во внешнем мире только после успешного завершения бизнеса. Поэтому лучше кэшировать события предметной области в сущностях и публиковать их после завершения бизнес-операций.
По сравнению с использованием ThreadLocal для управления подписчиками и подписки на обратные вызовы при публикации событий схема кэширования событий имеет очевидные преимущества.
3.4 Использование функции публикации событий IOC-контейнера
Контейнер IOC предоставляет нам множество функций, в том числе функцию публикации-подписки, такую как Spring.
В общем случае модель предметной области не должна напрямую зависеть от контейнера Spring. Поэтому в домене мы по-прежнему используем шину памяти, добавляем на нее подписчика и пробрасываем события с шины памяти в Spring-контейнер.
class SpringEventDispatcher implements ApplicationEventPublisherAware {
@Autowired
private DomainEventBus domainEventBus;
private ApplicationEventPublisher eventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.eventPublisher = applicationEventPublisher;
}
@PostConstruct
public void addListener(){
this.domainEventBus.register(event->true, event -> {this.eventPublisher.publishEvent(event);});
}
}
На этом этапе мы можем напрямую использовать механизм EventListener Spring для обработки событий предметной области.
@Component
public class RedisBasedQueueExporter {
@Autowired
private RedisBasedQueue redisBasedQueue;
@EventListener
public void handle(LikeSubmittedEvent likeSubmittedEvent){
SubmittedEvent submittedEvent = new SubmittedEvent();
submittedEvent.setOwner(likeSubmittedEvent.getSource().getOwner());
submittedEvent.setTarget(likeSubmittedEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(submittedEvent);
}
@EventListener
public void handle(LikeCancelledEvent likeCancelledEvent){
CanceledEvent canceledEvent = new CanceledEvent();
canceledEvent.setOwner(likeCancelledEvent.getSource().getOwner());
canceledEvent.setTarget(likeCancelledEvent.getSource().getTarget());
this.redisBasedQueue.pushLikeEvent(canceledEvent);
}
}
4 Резюме
- События предметной области — это факты, происходящие в предметной области, которые являются частью универсального языка.
- События предметной области предпочтительно используют модель публикации-подписки, которая публикует события и запускает соответствующие обработчики событий.
- Внутри ограниченного контекста преимущественно используются внутренние события и шина памяти, между ограниченными контекстами преимущественно используются внешние события и очереди сообщений.
- События домена упрощают асинхронные операции.
- События предметной области обеспечивают конечную согласованность между агрегатами.
- События предметной области могут упростить большие пакетные операции, превратив их во множество операций малого бизнеса.
- События предметной области могут дополнить мощное хранилище событий.
- События предметной области могут завершить интеграцию между ограниченными контекстами.
- События домена — это поддержка более сложных архитектур (CQRS).