Событийная модель приземления DDD

задняя часть Дизайн, управляемый доменом

Введение

всем привет. Не видел в день, например каждые 24 часа.

image.png

Я написал это на выходныхОдна статья приведет вас к земле DDD, я обнаружил, что все очень интересуются новыми областями и знаниями. Несколько статей серии DDD будут опубликованы позже, чтобы познакомить вас с некоторыми фактическими шагами, которые необходимо выполнить при миграции DDD mvc.

Блог серии DDD

  1. Одна статья приведет вас к земле DDD
  2. Событийная модель приземления DDD
  3. Складирование десанта DDD
  4. Многоуровневая архитектура лендинга DDD

Одна из концепций DDD заключается во всем, бизнес-границах и разделении. Когда я впервые не понял DDD, я подумал, что модель, управляемая событиями, может быть очень хороша для разделения системных функций. Конечно, это мое любимое блюдо, и я начал применять и глубже понимать событийно-ориентированную модель после того, как связался с DDD. На самом деле, будь то в среде Spring или в ежедневном процессе написания кода MVC, разумное использование модели, управляемой событиями, может значительно улучшить удобство сопровождения кода.

image.png

Таким образом, в этой статье будет представлено систематическое введение в использование модели, управляемой событиями, и подводные камни в DDD. Начиная с уровня приложений, это помогает каждому лучше выполнять миграцию архитектуры.

Мой первый буклет Nuggets«Углубленный ДДД»Он уже онлайн в Наггетс, приглашаем всех попробовать~

Я также создал группу WeChat DDD, так как QR-код нельзя разместить в статье, вы можете добавить меня в WeChat.baiyan_lou, Обратите внимание на обмен DDD, я втяну вас в группу, добро пожаловать на обмен и вместе добивайтесь прогресса.

2. Модель, управляемая событиями

2.1 Зачем нужна модель, управляемая событиями

Прежде чем использовать фреймворк и технологию, вы должны сначала узнать, в каких бизнес-сценариях нужно использовать эту вещь. Зачем и как его использовать важнее.

Предположим, теперь у нас есть относительно большая система заказов с одной услугой со следующими бизнес-требованиями:创建订单后,需要下发优惠券,给用户增长积分

Во-первых, давайте посмотрим, как большинство студентов пишут в одном сервисе. [Предположим, что заказы, купоны и баллы — это независимые услуги]

//在orderService内部定义一个放下
@Transactional(rollbackFor = Exception.class)
public void createOrder(CreateOrderCommand command){
  //创建订单
  Long orderId = this.doCreate(command);
  //发送优惠券
  couponService.sendCoupon(command,orderId);
  //增长积分
  integralService.increase(command.getUserId,orderId);
}

image.png

Будут ли какие-либо проблемы с запуском вышеуказанного кода онлайн? Не буду!

image.png

Так зачем его менять?

Причина в том, что в процессе непрерывной итерации бизнес-требований основной бизнес-процесс, не сильно связанный с текущим бизнесом, может быть заменен или модернизирован в любое время.

Продвижение Double 11, пользователям необходимо дать каждому пользователю несколько небольших подарков при оформлении заказа, затем необходимо написать еще одну функцию, которая сращивается после основного метода. Дубль 11 закончился, этот код нужно закомментировать. Идет большая акция на год, подарок меняется, а код приходится добавлять обратно. . . .

Взад и вперед логика порядка становится вонючей и длинной, а большое количество логики кода с комментариями нелегко читать и понимать.

image.png

Если используется модель, управляемая событиями, то при успешном создании заказа на первом этапе публикуется событие предметной области, указывающее, что заказ успешно создан. Служба купонов, поощрительная служба, дарение подарков и т. д. отслеживают это событие и соответствующим образом обрабатывают отслеживаемое событие.

код управляемой событиями модели

//在orderService内部定义一个放下
@Transactional(rollbackFor = Exception.class)
public void createOrder(CreateOrderCommand command){
  //创建订单
  Long orderId = this.doCreate(command);
  publish(orderCreateEvent);
}
​
//各个需要监听的服务
public void handlerEvent(OrderCreateEvent event){
//逻辑处理
}

image.png

Разделение кода в соответствии с принципом открытости-закрытости

2.2. Выбор модели на основе событий

2.2.1 Управляемый временем механизм в JDK

JDK предоставляет нам управляемые событиями (EventListener、EventObject), режим наблюдателя (Observer).

JDK не только предоставляетObservableсвоего рода,ObserverИнтерфейс поддерживает шаблон наблюдателя, а также предоставляетEventObject,EventListenerИнтерфейс для поддержки режима прослушивания событий.

Наблюдатель (Observer) эквивалентен прослушивателю событий (listener), наблюдаемое (Observable) эквивалентен источнику события и событию.Когда логика выполняется, наблюдатель может быть уведомлен о запуске обновления наблюдателя, и наблюдатель и параметры могут быть переданы одновременно. Упрощенная реализация шаблона прослушивания событий

// 观察者,实现此接口即可
public interface Observer {
  
  /**
  * 当被观察的对象发生变化时候,这个方法会被调用
  * Observable o:被观察的对象
  * Object arg:传入的参数
  **/
  void update(Observable o, Object arg);
}
​
// 它是一个Class
public class Observable {
​
  // 是否变化,决定了后面是否调用update方法
  private boolean changed = false;
  
  // 用来存放所有`观察自己的对象`的引用,以便逐个调用update方法
  // 需要注意的是:1.8的jdk源码为Vector(线程安全的),有版本的源码是ArrayList的集合实现; 
  private Vector<Observer> obs;
​
  public Observable() {
  obs = new Vector<>();
  }
​
  public synchronized void addObserver(Observer o); //添加一个观察者 注意调用的是addElement方法,   添加到末尾   所以执行时是倒序执行的
  public synchronized void deleteObserver(Observer o);
  public synchronized void deleteObservers(); //删除所有的观察者
​
  // 循环调用所有的观察者的update方法
  public void notifyObservers();
  public void notifyObservers(Object arg);
  public synchronized int countObservers() {
  return obs.size();
  }
​
  // 修改changed的值
  protected synchronized void setChanged() {
    changed = true;
  }
  
  protected synchronized void clearChanged() {
    changed = false;
  }
  
  public synchronized boolean hasChanged() {
    return changed;
  }
}

Очередь внутреннего наблюдателя передаетсяObservableсправиться с этим,并且,它是线程安全的. Однако этот метод не так удобен в использовании, нет шины сообщений, и вам нужно самостоятельно поддерживать наблюдателя и наблюдаемое. Для бизнес-систем также необходимо вести добавление каждого наблюдателя в отдельности.

2.2.2 Управляемый событиями механизм в spring

пружина обеспечивает после 4.2@EventListenerАннотация позволяет более удобно использовать мониторинг.

Учащиеся, знакомые с процессом запуска Spring, знают, что контейнер Spring будет выпущен после его обновления.ContextRefreshedEventсобытие, поэтому, если нам нужно отслеживать это событие, мы можем напрямую написать класс слушателя.

@Slf4j
@Component
public class ApplicationRefreshedEventListener implements   ApplicationListener<ContextRefreshedEvent> {
​
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        //解析这个事件,做你想做的事,嘿嘿
    }
}

Точно так же мы можем сами определить событие,ApplicationEventPublisherОтправить.

/**
 * 领域事件基类
 *
 * @author baiyan
 * @date 2021/09/07
 */
@Getter
@Setter
@NoArgsConstructor
public abstract class BaseDomainEvent<T> implements Serializable {
​
    private static final long serialVersionUID = 1465328245048581896L;
​
    /**
     * 领域事件id
     */
    private String demandId;
​
    /**
     * 发生时间
     */
    private LocalDateTime occurredOn;
​
    /**
     * 领域事件数据
     */
    private T data;
​
    public BaseDomainEvent(String demandId, T data) {
        this.demandId = demandId;
        this.data = data;
        this.occurredOn = LocalDateTime.now();
    }
​
}

Определите единую бизнес-шину для отправки событий

/**
 * 领域事件发布接口
 *
 * @author baiyan
 * @date  2021/09/07
 */
public interface DomainEventPublisher {
​
    /**
     * 发布事件
     *
     * @param event 领域事件
     */
    void publishEvent(BaseDomainEvent event);
​
}
/**
 * 领域事件发布实现类
 *
 * @author baiyan
 * @date  2021/09/07
 */
@Component
@Slf4j
public class DomainEventPublisherImpl implements DomainEventPublisher {
​
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;
​
    @Override
    public void publishEvent(BaseDomainEvent event) {
        log.debug("发布事件,event:{}", event.toString());
        applicationEventPublisher.publishEvent(event);
    }
​
}

прослушать событие

@Component
@Slf4j
public class UserEventHandler {
​
    @EventListener
    public void handleEvent(DomainEvent event) {
       //doSomething
    }
​
}

Уху, взлетай~

image.png

По сравнению с управляемой событиями моделью наблюдателя, предоставляемой JDK, метод, предоставляемый spring, — это yyds.

2.3 Управление транзакциями на основе событий

Обычно мы публикуем событие после завершения хранения некоторых данных. В последующем мы запишем запись операции в запись es, но в это время у es может истечь время ожидания ответа кластера, и хранилище записей операций не смогло сообщить об ошибке. Однако с точки зрения бизнес-логики сбой хранения записей операций не должен влиять на логику выполнения основного процесса, требующего независимости от транзакций. Или, если есть ошибка в выполнении основного процесса, то нам нужно вызвать событие для отправки сообщений DingTalk в группу для онлайн-мониторинга бизнеса, и нам нужно бросить исключение в логике основного метода, а затем вызвать это событие. В это время, если мы используем@EventListener, реализация вышеперечисленных бизнес-сценариев представляет собой более хлопотную логику.

Для решения вышеуказанных проблем Spring предоставляет нам два способа:

(1)@TransactionalEventListenerаннотация.

(2) Менеджер синхронизации транзакцийTransactionSynchronizationManager.

Эта статья направлена ​​на@TransactionalEventListenerПроведите анализ.

Сразу видно из названия, что этоEventListener, в Spring 4.2+ есть метод, называемый@TransactionEventListenerМетод может реализовать обработку события при управлении транзакцией.

//被@EventListener标注,表示它能够监听事件
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EventListener
public @interface TransactionalEventListener {
​
  //表示当前事件跟随消息发送方事务的出发时机,默认为消息发送方事务提交之后才进行处理。
   TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
​
   //true时不论发送方是否存在事务均出发当前事件处理逻辑
   boolean fallbackExecution() default false;
​
   //监听的事件具体类型,还是建议指定一下,避免监听到子类的一些情况出现
   @AliasFor(annotation = EventListener.class, attribute = "classes")
   Class<?>[] value() default {};
​
   //指向@EventListener对应的值
   @AliasFor(annotation = EventListener.class, attribute = "classes")
   Class<?>[] classes() default {};
​
   //指向@EventListener对应的值
   String condition() default "";
​
}
public enum TransactionPhase {
   // 指定目标方法在事务commit之前执行
   BEFORE_COMMIT,
​
   // 指定目标方法在事务commit之后执行
    AFTER_COMMIT,
​
    // 指定目标方法在事务rollback之后执行
    AFTER_ROLLBACK,
​
   // 指定目标方法在事务完成时执行,这里的完成是指无论事务是成功提交还是事务回滚了
   AFTER_COMPLETION
  }

Мы знаем, что механизм прослушивания событий Spring (модель публикации и подписки) на самом деле не является асинхронным (по умолчанию), а синхронным для разделения кода. и@TransactionEventListenerОн по-прежнему таким, но для его решения добавлен метод обратного вызова, чтобы событие могло быть обработано только тогда, когда транзакция Commited, Rollback и т. д., для достижения цели синхронизации транзакций.

3. Тренируйтесь и ступайте на яму

Для событийно-ориентированной модели@TransactionEventListenerи@EventListenerПредположим, два бизнес-сценария.

Добавьте новых пользователей, свяжите роли и добавьте связанные записи операций авторизации ролей.

1.统一事务: Вышеупомянутые три операции и транзакции интегрированы.Независимо от того, какая из них является ненормальной, данные будут откатываться единообразно.

2独立事务: Вышеупомянутые три операции независимы. После того, как событие будет выпущено, любые последующие исключения не будут затронуты.

3.1. Единая транзакция

Пользователь добавлен

@Service
@Slf4j
public class UserServiceImpl implements UserService {
​
    @Autowired
    DomainEventPublisher domainEventPublisher;
​
    @Transactional(rollbackFor = Exception.class)
    public void createUser(){
        //省略非关键代码
        save(user);
        domainEventPublisher.publishEvent(userEvent);
    }
}

Ассоциация роли пользователя

@Component
@Slf4j
public class UserEventHandler {
​
    @Autowired
    DomainEventPublisher domainEventPublisher;
​
    @Autowired
    UserRoleService userRoleService;
​
    @EventListener
    public void handleEvent(UserEvent event) {
        log.info("接受到用户新增事件:"+event.toString());
        //省略部分数据组装与解析逻辑
        userRoleService.save(userRole);
        domainEventPublisher.publishEvent(userRoleEvent);
    }
​
}

Запись операции роли пользователя

@Component
@Slf4j
public class UserRoleEventHandler {
​
    @Autowired
    UserRoleRecordService userRoleRecordService;
​
    @EventListener
    public void handleEvent(UserRoleEvent event) {
        log.info("接受到userRole事件:"+event.toString());
        //省略部分数据组装与解析逻辑
        userRoleRecordService.save(record);
    }
​
}

Выше приведена логика той же транзакции.Если в каком-либо методе возникнет исключение, вся логика вставки данных будет отменена.

image.png

дать заключение,@EventListenerОтмеченный метод добавляется в логику выполнения текущей транзакции и интегрируется с транзакцией основного метода.

Шаг в яму 1:

Строго говоря, это не для того, чтобы отделить основную логику от бизнеса, или в синхронной транзакции.Конечно, это тоже имеет сценарии адаптации.Можно сделать это ради простоты кода и ясности логики на уровне функций. Но это не так DDD.Метод службы приложений в DDD - это вариант использования, который проходит через логику основного процесса.Поскольку это бизнес с сильной согласованностью в текущей системе, он должен быть отражен в службе приложений . Конечно, это деловая граница. Например, пользователи и наделение полномочиями явно не являются строго согласованными операциями. Сбой наделения полномочиями не должен влиять на моих новых пользователей. Поэтому в этом сценарии не рекомендуется использовать унифицированные транзакции для преобразования DDD.

Ступайте на яму 2:

Логика выполнения в прослушивателе может занимать много времени и должна обрабатываться асинхронно.UserEventHandlerметка на методе@Async, то он изолируется от транзакции метода основной логики, а транзакция в слушателе начинает быть независимой и не будет влиять на транзакцию в userService. Например, когда другие коды остаются неизменными, код службы роли пользователя изменяется следующим образом.

@Component
@Slf4j
public class UserEventHandler {
​
    @Autowired
    DomainEventPublisher domainEventPublisher;
​
    @Autowired
    UserRoleService userRoleService;
​
    @EventListener
    @Async
    public void handleEvent(UserEvent event) {
        log.info("接受到用户新增事件:"+event.toString());
        //省略部分数据组装与解析逻辑
        userRoleService.save(userRole);
        domainEventPublisher.publishEvent(userRoleEvent);
        throw new RuntimeException("制造一下异常");
    }
​
}

Выяснилось, что пользователь добавлен, отношение роли пользователя добавлено, но запись операции не добавлена. Первый результат понятен, а вот второй странный: в обработчике событий было выброшено исключение, но данные успешно сохранились.

Это на самом деле потому, чтоUserEventHandlerизhandleEventВнешний слой метода вложен@Transactional,userRoleService.saveПосле завершения операции транзакция фиксируется, и последующие исключения не затрагиваются. Чтобы сохранить согласованность транзакции, добавьте@TransactionalВот и все.

3.2. Независимые дела

@EventListener в качестве драйвера для загрузки децентрализованного управления кодом — это неплохо. Однако на уровне DDD данные транзакций смешиваются между собой.Помимо проблем, найти слой за слоем проблематично, а пакетов данных много.Рекомендуется использовать его.@TransactionalEventListene

Пользователь добавлен

@Service
@Slf4j
public class UserServiceImpl implements UserService {
​
    @Autowired
    DomainEventPublisher domainEventPublisher;
​
    @Transactional(rollbackFor = Exception.class)
    public void createUser(){
        //省略非关键代码
        save(user);
        domainEventPublisher.publishEvent(userEvent);
    }
}

Ассоциация роли пользователя

@Component
@Slf4j
public class UserEventHandler {
​
    @Autowired
    DomainEventPublisher domainEventPublisher;
​
    @Autowired
    UserRoleService userRoleService;
​
    @TransactionalEventListener
    public void handleEvent(UserEvent event) {
        log.info("接受到用户新增事件:"+event.toString());
        //省略部分数据组装与解析逻辑
        userRoleService.save(userRole);
        domainEventPublisher.publishEvent(userRoleEvent);
    }
​
}

Запись операции роли пользователя

@Component
@Slf4j
public class UserRoleEventHandler {
​
    @Autowired
    UserRoleRecordService userRoleRecordService;
​
    @TransactionalEventListener
    public void handleEvent(UserRoleEvent event) {
        log.info("接受到userRole事件:"+event.toString());
        //省略部分数据组装与解析逻辑
        userRoleRecordService.save(record);
    }
​
}

тот же код, измените аннотацию с@EventListenerзаменить@TransactionalEventListener. После казни обнаружилась волшебная проблема,Запись операции роли пользователяДанные не сохраняются! ! !

image.png

Взгляните на логику и измените аннотацию, и возникает эта проблема.Сравните разницу между двумя аннотациями.@TransactionalEventListenerНезависимо от транзакции, с аннотациями по умолчаниюphaseЗначение параметраTransactionPhase.AFTER_COMMIT, то есть метод основной логики выполняется после фиксации транзакции. И мы знаем, что код ключа фиксации транзакции весной находится вAbstractPlatformTransactionManager.commitTransactionAfterReturning

protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
   if (txInfo != null && txInfo.getTransactionStatus() != null) {
      if (logger.isTraceEnabled()) {
         logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
      }
      //断点处
      txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
   }
}

Добавьте следующую конфигурацию в файл конфигурации

logging:
  level:
    org:
      mybatis: debug

Поставьте точку останова на место вышеприведенного кода и снова выполните логику.

нашел, впервыеuserServiceСохраните данные в эту точку останова, затем вuserRoleService.saveЛогика, здесь не прописана точка останова и не прописан метод обработки события последующей записи операции.

Давайте посмотрим на журнал

- 2021-09-07 19:54:38.166, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
- 2021-09-07 19:54:38.166, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@77a74846]
- 2021-09-07 19:54:38.167, DEBUG, [,,], [http-nio-8088-exec-6], o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1832a0d9] will be managed by Spring
- 2021-09-07 19:54:38.184, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@77a74846]
- 2021-09-07 19:54:51.423, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@77a74846]
- 2021-09-07 19:54:51.423, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@77a74846]
- 2021-09-07 19:54:51.423, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@77a74846]
- 2021-09-07 19:54:51.430,  INFO, [,,], [http-nio-8088-exec-6], com.examp.event.demo.UserEventHandler - 接受到用户新增事件:com.examp.event.demo.UserEvent@385db2f9
- 2021-09-07 19:54:53.602, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Creating a new SqlSession
- 2021-09-07 19:54:53.602, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@9af2818] was not registered for synchronization because synchronization is not active
- 2021-09-07 19:54:53.603, DEBUG, [,,], [http-nio-8088-exec-6], o.m.s.t.SpringManagedTransaction - JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1832a0d9] will be managed by Spring
- 2021-09-07 19:54:53.622, DEBUG, [,,], [http-nio-8088-exec-6], org.mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@9af2818]

Обратите внимание на получение用户新增事件После журнала,SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@9af2818] was not registered for synchronization because synchronization is not activeОбъясните, что текущее событие является логикой отсутствия выполнения транзакции. оглянуться назад снова@TransactionalEventListener, конфигурация по умолчанию такова, что событие выполняется после фиксации транзакции, но здесь нет транзакций, поэтому, естественно, событие не будет запущено.

Посмотрите на картинку и посмотрите на логику кода

image-20210907200823192.png

Итак, как решить вышеуказанную проблему?

На самом деле, эта вещь относительно проста:

1. Вы можете обозначить логику, которая слушает это событие, не задумываясь@TransactionalEventListener(fallbackExecution = true), событие запускается независимо от того, есть ли у отправителя события транзакция.

2. Отметьте одно поверх второго события публикации.@Transactional(propagation = Propagation.REQUIRES_NEW), помните, что не следует прямо помечать@Transactional, так как транзакция в userService была зафиксирована, и@TransactionalМеханизм распространения транзакций по умолчанию:Propagation.REQUIRED, Если текущей транзакции нет, создать новую транзакцию, если транзакция уже есть, присоединиться к транзакции.

Транзакция в userService все еще существует, но она была зафиксирована и не может быть добавлена, а значит, запись операции все еще не может быть вставлена.

Измените конфигурацию на

logging:
  level:
    org: debug

Лог можно посмотреть

- 2021-09-07 20:26:29.900, DEBUG, [,,], [http-nio-8088-exec-2], o.s.j.d.DataSourceTransactionManager - Cannot register Spring after-completion synchronization with existing transaction - processing Spring after-completion callbacks immediately, with outcome status 'unknown'

4. Приложения, управляемые событиями, в DDD

После понимания модели, управляемой событиями в Spring, все, что нам нужно сделать, — это начать отделять бизнес-логику.

Уточнение бизнес-вариантов использования с помощью штормов событий, проектирования и полных сводных корней【ps:其实我觉得设计聚合根是最难的,业务边界是需要团队成员达成共识的地方,不是研发说了算的】, разделить границы бизнес-домена и выполнить различные логики, изначально смешанные в сервисе, в соответствии с корнем агрегации:

  1. Для каждой командной операции агрегации выпускается как минимум одно предметное событие, указывающее на результат выполнения операции
  2. Каждое событие домена будет сохранено в хранилище событий
  3. Когда агрегат извлекается из репозитория, агрегат перестраивается на основе событий, произошедших с агрегатом, и события воспроизводятся в том же порядке, в котором они были созданы.
  4. Агрегированный снимок: Сериализация и сохранение моментального снимка состояния агрегированного события при его возникновении.

5. Резюме

В этом документе основное внимание уделяется концепции и применению модели, управляемой событиями, а также анализу и устранению ошибок в реальной бизнес-логике, которые могут возникнуть. Наконец, в нем анализируется, как реализовать приведенную выше модель, управляемую событиями, в DDD.

Конечно, я думаю, что у всех должно быть четкое представление о модели событий здесь, но применение в DDD все еще немного расплывчато. Тысячи слов слились в одно предложение:Связано с базовой логикой агрегации, используйте оркестровку службы приложений, не связанную с базовой логикой, следуйте модели, управляемой событиями, и используйте независимый режим транзакций.. Что касается согласованности данных, то это решается в соответствии с вашим собственным бизнесом, и методы и подводные камни вам были сообщены.

Мы с тобой оба архитекторы! ! !

image.png

6. Цитаты и ссылки

Принцип использования и реализации @TransactionalEventListener

[Xiaojia Spring] Начиная с управляемого событиями механизма (ApplicationEvent) в Spring, давайте поговорим о [Режиме наблюдателя] [Режиме слушателя] [Режиме публикации-подписки] [Очереди сообщений MQ] [Источнике событий]...

7. Свяжитесь со мной

Если есть неточности в тексте, поправьте меня, текст писать непросто, ставьте лайк, ладно~

Диндин: louyanfeng25

WeChat: baiyan_lou

Общественный номер: дядя Бай Ян

image.png