0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

为什么需要Repository?什么才是好的Repository ?

jf_ro2CN3Fa 来源:geekhalo 2023-03-07 09:11 次阅读

1. 领域事件

领域事件是 DDD 中重要的模式之一,主要用于模型或系统间的解耦,提高系统的可扩展性和可维护性。

1.1. 什么是领域事件

领域事件是领域驱动设计(Domain-Driven Design,简称DDD)中的一个重要概念,特指在领域模型中发生的有意义的事件,是对领域模型中的重要业务动作执行结果的抽象,如订单创建、支付完成等。

在DDD中,领域事件是一种用于传递信息的机制,它使得不同领域模型之间的通信变得更加简单和灵活。通过将事件分发给相关的订阅者,可以让不同的领域模型之间实现松耦合,从而更容易扩展和维护应用程序。

领域事件通常由领域对象主动触发并发布,而事件处理器则负责订阅事件并对事件进行处理。通过事件发布和订阅机制,可以在应用程序中实现高效的事件驱动架构,从而更好地支持复杂的业务逻辑和业务流程。

说起来有点抽象,简单举个例子:假设有一个电子商务系统,用户下单后需要生成订单并发送通知给相关人员。在领域模型中,可以定义一个 Order 领域对象,该对象可以包含多个属性,如订单号、下单时间、购买的商品信息、收货地址等等。当用户下单时,可以通过调用 Order 对象的方法来生成订单,同时也可以通过领域事件来发送通知。

具体来说,可以定义一个 OrderCreated 领域事件,用于表示订单创建完成的事件,该事件包含一些必要的属性,如订单号、下单时间、购买的商品信息、收货地址等等。当 Order 对象创建完成后,可以通过领域事件来触发发送通知的操作,比如发送邮件或短信通知相关人员。

1.2. 领域事件的应用场景

领域事件的应用创建众多,从图中可以看出:

04b8ee02-bc7e-11ed-bfe3-dac502259ad0.png

领域事件可以:

保证聚合间的数据一致性。当一个聚合根上的操作引发了其他聚合根的变更时,将这些变更作为领域事件发布出去,其他聚合根可以订阅这些事件并更新自己的状态,从而实现最终一致性。

替换批量处理。可以作为任务的触发器,例如定时任务、异步任务,避免定时+扫描这类批量处理。

实现事件源模式。将所有的领域事件全部存储下来,可以用于恢复聚合的状态,实现事件源模式;也可以用于后续的审计和调试。

进行限界上下文集成。将事件从一个子域发布到另一个子域,使得这两个子域可以解耦,不用相互知道彼此的存在。

领域事件虽好,但仍需技术框架进行支持,其实 Spring 的 Event 机制就足以满足各类需求。

2. Spring 对 Event 的支持

在 Spring 中,事件的处理可以通过三种方式来实现:

基于接口的事件处理:通过实现 ApplicationListener 接口并重写 onApplicationEvent 方法来处理事件。

基于注解的事件处理:通过在方法上添加 @EventListener 或 @TransactionEventListener 注解来处理事件,可以指定事件的类型以及监听的条件等。

基于异步事件处理:通过使用 @Async 注解来异步处理事件,可以提高应用程序的响应速度。

2.1. 基于接口的事件处理

由于与 Spring 存在强耦合,现在已经很少使用,可以直接跳过。

下面是一个基于接口的事件处理的示例代码:

@Component
publicclassMyEventListenerimplementsApplicationListener{
@Override
publicvoidonApplicationEvent(MyEventevent){
//处理事件
System.out.println("Receivedevent:"+event.getMessage());
}
}

publicclassMyEvent{
privateStringmessage;

publicMyEvent(Stringmessage){
this.message=message;
}

publicStringgetMessage(){
returnmessage;
}
}

@Component
publicclassMyEventPublisher{
@Autowired
privateApplicationEventPublishereventPublisher;

publicvoidpublishEvent(Stringmessage){
MyEventevent=newMyEvent(message);
eventPublisher.publishEvent(event);
}
}

在这个示例中,MyEvent 是一个自定义的事件类,MyEventListener 是一个实现了 ApplicationListener 接口的监听器,用于处理 MyEvent 事件,MyEventPublisher 是用于发布事件的类。

当应用程序调用 MyEventPublisher 的 publishEvent 方法时,会触发一个 MyEvent 事件,MyEventListener 中的 onApplicationEvent 方法将被自动调用,从而处理这个事件。

2.2. 基于注解的事件处理

Spring 提供 @EventListener 和 @TransactionListener 两个注解以简化对事件的处理。

2.2.1. @EventListener

Spring 的 EventListener 监听器是一种相对于传统的事件监听方式更为简洁和灵活的事件机制。与传统的事件机制不同,EventListener 不需要显示地继承特定的事件接口,而是使用注解标识需要监听的事件类型,然后通过一个单独的监听器类处理所有类型的事件。

相比之下 EventListener 的优势主要有以下几点:

更加灵活:EventListener 不依赖于任何特定的事件接口,从而使得事件处理更加灵活,可以监听和处理任意类型的事件。

更加简洁:相比传统的事件监听方式,使用 EventListener 可以避免一系列繁琐的接口定义和实现,简化了代码结构,使得开发效率更高。

更加松耦合:EventListener 将事件发布方和事件处理方分离,遵循松耦合的设计原则,提高了代码的可维护性和扩展性。

更加可测试:由于 EventListener 可以监听和处理任意类型的事件,可以通过单元测试验证其功能是否正确,从而提高了测试的可靠性。

以下是一个简单的例子:

@Component
publicclassMyEventListener{

@EventListener
publicvoidonApplicationEvent(MyEventevent){
//处理事件
System.out.println("Receivedevent:"+event.getMessage());
}
}

publicclassMyEvent{
privateStringmessage;

publicMyEvent(Stringmessage){
this.message=message;
}

publicStringgetMessage(){
returnmessage;
}
}

@Component
publicclassMyEventPublisher{

@Autowired
privateApplicationEventPublishereventPublisher;

publicvoidpublishEvent(Stringmessage){
MyEventevent=newMyEvent(message);
eventPublisher.publishEvent(event);
}
}

相比基于接口的事件处理,EventListener 是一种更加简洁、灵活、松耦合、可测试的事件机制,能够有效地降低开发的复杂度,提高开发效率。

2.2.2. @TransactionEventListener

在 Spring 中,TransactionEventListner 和 EventListner 都是用于处理事件的接口。不同之处在于

TransactionEventListner 是在事务提交后才会触发

而 EventListner 则是在事件发布后就会触发。

具体来说,在使用 Spring 的声明式事务时,可以在事务提交后触发某些事件。这就是 TransactionEventListner 的应用场景。而 EventListner 则不涉及事务,可以用于在事件发布后触发一些操作。

下面是一个简单的示例,演示了如何使用 TransactionEventListner 和 EventListner:

@Component
publicclassMyEventListener{

@EventListener
publicvoidhandleMyEvent(MyEventevent){
//处理MyEvent
}

@TransactionalEventListener
publicvoidhandleMyTransactionalEvent(MyTransactionalEventevent){
//处理MyTransactionalEvent
}
}

@Service
publicclassMyService{

@Autowired
privateApplicationEventPublishereventPublisher;

@Autowired
privateMyRepositorymyRepository;

@Transactional
publicvoiddoSomething(){
//做一些事情
MyEntityentity=myRepository.findById(1L);
//发布事件
eventPublisher.publishEvent(newMyEvent(this,entity));
//发布事务事件
eventPublisher.publishEvent(newMyTransactionalEvent(this,entity));
}
}

在这个例子中,MyEventListener 类定义了两个方法,handleMyEvent 和 handleMyTransactionalEvent,分别处理 MyEvent 和 MyTransactionalEvent 事件。其中,handleMyTransactionalEvent 方法用 @TransactionalEventListener 注解标记,表示它只会在事务提交后触发。

MyService 类中的 doSomething 方法使用 ApplicationEventPublisher 来发布事件。注意,它发布了两种不同类型的事件:MyEvent 和 MyTransactionalEvent。这两个事件会分别触发 MyEventListener 中的对应方法。

总的来说,Spring 的事件机制非常灵活,可以方便地扩展应用程序的功能。TransactionEventListner 和 EventListner 这两个接口的应用场景有所不同,可以根据实际需求选择使用。

2.3.基于异步事件处理

@Async是Spring框架中的一个注解,用于将一个方法标记为异步执行。使用该注解,Spring将自动为该方法创建一个新线程,使其在后台异步执行,不会阻塞主线程的执行。

在具体应用中,使用@Async可以大大提升应用的并发处理能力,使得系统能够更快地响应用户请求,提高系统的吞吐量。

@Async 和 @EventListener 或 @TransactionEventListener 注解在一起使用时,会产生异步的事件处理器。使用这种组合的方式,事件处理器会在单独的线程池中执行,以避免阻塞主线程。这种方式在需要处理大量事件或者事件处理器耗时较长的情况下非常有用,可以有效提高应用的性能和可伸缩性。同时,Spring 框架对这种方式也提供了完善的支持,可以方便地使用这种方式来实现异步事件处理。

下面是一个简单的示例代码,演示了如何在 Spring 中使用 @Async 和 @EventListener 一起实现异步事件处理:

@Component
publicclassExampleEventListener{

@Async
@EventListener
publicvoidhandleExampleEvent(ExampleEventevent){
//在新的线程中执行异步逻辑
//...
}
}

在这个示例中,ExampleEventListener 类中的 handleExampleEvent 方法使用了 @Async 和 @EventListener 注解,表示这个方法是一个异步事件监听器。当一个 ExampleEvent 事件被触发时,这个方法会被异步地执行。在这个方法中,可以执行任何异步的逻辑处理,比如向队列发送消息、调用其他服务等。

备注:在使用 @Async 时,需要根据业务场景对线程池进行自定义,以免出现资源不够的情况(Spring 默认使用单线程处理@Async异步任务)

4. 场景分析

综上所述,当领域事件发出来之后,不同的注解会产生不同的行为,简单汇总如下:

@EventListener @TransactionEventListener
无 @Async 顺序、同步执行 事务提交后、同步执行
有 @Async 顺序、异步执行 事务提交后、异步执行

4.1. @EventListener

04e6afea-bc7e-11ed-bfe3-dac502259ad0.png

特点:

顺序执行。调用 publish(Event) 后,自动触发对 @EventListner 注释方法的调用

同步执行。使用主线程执行,方法抛出异常会中断调用链路,会触发事务的回归

应用场景:

事务消息表。在同一事务中完成对业务数据和消息表的修改

业务验证。对业务对象进行最后一次验证,如果验证不通过直接抛出异常中断数据库事务

业务插件。在当前线程和事务中执行插件完成业务扩展

4.2. @TransactionEventListener

04f892e6-bc7e-11ed-bfe3-dac502259ad0.png

特点:

事务提交后执行。调用 publish(Event) 时,只是向上下文中注册了一个回调器,并不会立即执行;只有在事务提交后,才会触发对 @TransactionEventListner 注释方法的调用

同步执行。使用主线程执行,方法抛出异常会中断调用链路,当不会回归事务(事务已提交,没有办法进行回归)

应用场景:

数据同步。事务提交后,将变更同步到 ES 或 Cache

记录审计日志。只有在业务变更成功更新到数据库时才进行记录

备注:@TransactionEventLisnter 必须在事务上下文中,脱离上下文,调用不会生效

4.3. @EventListener + @Async

0509c2b4-bc7e-11ed-bfe3-dac502259ad0.png

特点:

顺序执行。调用 publish(Event) 后,自动触发对 @EventListner 注释方法的调用

异步执行。使用独立的线程池执行任务,方法抛出异常对主流程没有任何影响

应用场景:

记日志明细日志,辅助排查问题

4.4. @TransactionEventListener + @Async

051f67cc-bc7e-11ed-bfe3-dac502259ad0.png

特点:

事务提交后执行。调用 publish(Event) 时,只是向上下文中注册了一个回调器,并不会立即执行;只有在事务提交后,才会触发对 @TransactionEventListner 注释方法的调用

异步执行。使用独立的线程池执行任务,方法抛出异常对主流程没有任何影响

应用场景:异步处理。记录操作日志,异步保存数据等 备注:@TransactionEventLisnter 必须在事务上下文中,脱离上下文,调用不会生效

5. 小结

领域事件的落地,不仅需要强大的设计能力,还需要与之匹配的基础设施。Spring 作为最常用的框架,基于发布订阅实现了完整的一套 Event 管理机制。工具在手是否能根据业务场景选择合适的解决方案就成了研发的职责,简单思考以下组合适用场景是什么:

@EventListener

@TransactionEventListener

@EventListener + @Async

@TransactionEventListener + @Async






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2957

    浏览量

    104535
  • MySQL
    +关注

    关注

    1

    文章

    801

    浏览量

    26437
  • MYSQL数据库
    +关注

    关注

    0

    文章

    95

    浏览量

    9380
  • ddd
    ddd
    +关注

    关注

    0

    文章

    23

    浏览量

    2918

原文标题:Spring Event + DDD = 王炸!!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    modusToolbox在加载离线库时,总是在loading device_db时提示错误的原因?

    /https___github.com_Infineon_device-db/device-db\' does not appear to be a git repository fatal: Could not read from
    发表于 02-02 08:07

    使用MotorControl Workbench生成代码时出错的原因?

    各位大师你们!使用MotorControl Workbench生成代码时出错,请高手指教,谢谢。 Code generation started CMSIS Pack version
    发表于 04-18 07:08

    How to download and install AD10 when using proxy servers

    create your own installation repository. This provides a local install solution for computers
    发表于 09-05 10:54

    MATLAB请教各位大神

    ModelAdvisor.Repository/connect (line 14)出错 ModelAdvisor.Repository (line 14)出错 Simulink.ModelAdvisor/getWorkDir
    发表于 10-24 18:42

    插牌具有什么功能才是需要的?

    插牌具有什么功能才是需要的?
    发表于 07-13 13:57

    ESP-IDF使用:请问怎么样能测试esp32的spp example

    各位大佬指点迷津。$ makefatal: Not a git repository (or any of the parent directories): .gitfatal: Not a git
    发表于 08-17 09:22

    STM32CubeMX芯片包(固件库)使用注意事项

    1. 解压本地固件库包后需要将文件夹放到指定路径,该指定路径为cubemx软件中的help -> updater settings -> updater settings ->
    发表于 07-23 09:03

    STM32CubeIDE不显示“Inizialize all peripherals with their Default Mode”怎么解决?

    大家,我有这个痛苦的问题:当我想创建一个新项目时,选择板子和项目名称后点击“完成”并没有显示可以选择加载外设默认模式的窗口,而是立即加载空白配置。我试过其他版本的多维数据集并删除“\Repository”但没有任何改变。请问有什么建议吗?
    发表于 01-03 09:50

    STM32CubeMX系列6版本删除主文件夹中存在的所有文件夹,从而删除用户创建的文件夹要如何避免?

    我将 STM32CubeMX 安装到文件夹C:\STM32CubeMX,然后将 REPOSITORY FOLDER 设置为C:\STM32CubeMX\Repository,因为我想将
    发表于 01-13 08:32

    如何在Touchgfx设计器中开始使用来自touchgfx-open-repository的图形小部件?

    代码让它们做一些事情。我看不到添加图表的方法,所以我用谷歌搜索了一下,发现您需要来自此处存储库的图表小部件:https ://github.com/touchgfx
    发表于 01-30 08:09

    如何在代码中从touchgfx-open-repository添加二维码小部件?

    ) 、 qrCode.setQRCode(&code) 、 qrCode.setScale(4) 、 add(qrCode) 方法,但我找不到它。https://github.com/touchgfx/touchgfx-open-repository/tree/master/widgets/QRCode你能帮忙吗?
    发表于 02-08 07:44

    【米尔MYD-JX8MMA7开发板-ARM+FPGA架构试用体验】快速入门

    : The repository 'http://ftp.debian.org/debian buster InRelease' is not signed.N: Updating from such a
    发表于 03-09 19:04

    mirror和repository的区别分析

    internal repository是指在局域网内部搭建的repository,它跟central repository, jboss repository等的区别仅仅在于其URL是
    发表于 11-29 10:42 4220次阅读
    mirror和<b class='flag-5'>repository</b>的区别分析

    怎么样才是一台电脑

    怎么样才是一台电脑?即使是天天进行测试的的编辑,都要不断地面对和解决这个问题。性能,稳定性,兼容性,平台能力,都对都很重要,而单独强调某一个侧面却都不够全面。
    发表于 05-17 14:46 1675次阅读

    pip安装更换镜像

    使用pip来安装python包有时候安装起来会非常慢,因此需要换成国内的源来加速下载: 使用命令 以Torch为例: pip install -i https
    的头像 发表于 01-11 14:54 590次阅读