ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

利用springEvent,进行服务内部领域事件处理

利用springEvent,进行服务内部领域事件处理 SpringEvent的最佳实践文章目录SpringEvent的最佳实践前言一、领域事件是什么二、使用步骤1.事务提交之后才发送domainEvent2.异步处理事件三、新的注解更优雅的解法3.1 发送domainEvent时机不需要特殊处理3.2 使用TransactionalEventListener总结前言相信java boy对于Spring Event多多少少都有一些熟悉了。如果不了解的可以参考我的这篇文章Spring的Event编程以及实现原理一、领域事件是什么简单说领域事件其实就是对PO的封装。我这边要说明的并不是DDD对系统落地的改造。而是借鉴DDD的思想对用户操作的实体-entity进行封装成一个domainEvent结构。有了这个domainEvent有两个好处可以对entity进行防腐。该domainEvent作为后续操作的dto不关注如何通过entity构建的domainEvent只需要基于该domainEvent进行操作即可可以提高并行开发的效率。对entity操作的代码和后续操作的代码可以让不同的研发同时进行二、使用步骤针对B端流量并没有这么大的服务没有必要考虑分布式事务。但是接入一个自产自销的MQ感觉又有点过重。还不如SpringEvent来自己处理1.事务提交之后才发送domainEvent我们需要考虑的问题是对于entity进行的操作一定是写库操作必然涉及到事务。针对业务流程来说必然需要保证事务提交成功之后再进行后续操作此时可以使用Spring事务的钩子函数让事务提交成功之后才可以进行操作。Transactional(rollbackForException.class)publicvoiddoProcess(Useruser){// 1.根据entity构建domainEvent的dto作为防腐层UserMsgBOuserMsgBObuildMsgBO(user);// 2. 注册事务钩子函数让事务提交之后进行Event的publishTransactionSynchronizationManager.registerSynchronization(newTransactionSynchronization(){OverridepublicvoidafterCommit(){log.info(publish event, userMsgBO:{},JSON.toJSONString(userMsgBO));applicationEventPublisher.publishEvent(userMsgBO);}});}//3. 数据库操作2.异步处理事件需要考虑日志打印问题根据Spring的Event编程以及实现原理文章里写的如果要进行事件处理的异步进行需要进行实例化一个SimpleApplicationEventMulticaster并设置其taskExecutor属性。实际上呢还可以使用Async注解来实现Async的使用及原理Async(springEventExecutor)EventListener(UserMsgBO.class)publicvoiddoSendMsg(UserMsgBOuserMsgBO){log.info(doSendMsg, msg:[{}],JSON.toJSONString(userMsgBO));try{// 具体的处理逻辑}catch(Exceptione){log.error(doSendMsg error, msg:[{}],JSON.toJSONString(userMsgBO),e);}}可以看到此时给Async注解传入了一个线程池springEventExecutor。还差最后一步怎么第二节提出的问题使用了线程池实现了异步那么ThreadLocal里的traceId是不是丢了呢相信大家应该都知道阿里的TransmittableThreadLocal也知道具体的场景Bean(springEventExecutor)publicExecutorspringEventExecutor(){ThreadPoolExecutorthreadPoolExecutornewThreadPoolExecutor(10,10,60L,TimeUnit.SECONDS,newLinkedBlockingQueue(1000),newThreadFactoryBuilder().setNameFormat(spring-event-thread-pool-%d).build(),newThreadPoolExecutor.CallerRunsPolicy());// 利用TtlExecutors包装一下线程池可以在日志中打印traceIdreturnTtlExecutors.getTtlExecutorService(threadPoolExecutor);}当然保险起见还可以针对消费domainEvent的doSendMsg方法增加一个后门防止服务重启时打断异步流程。当然如果对于数据一致性要求比较高那么就借鉴B端系统的最终一致性方案了可以将domainEvent进行持久化然后通过定时任务来保证最终一致性。三、新的注解更优雅的解法3.1 发送domainEvent时机不需要特殊处理Transactional(rollbackForException.class)publicvoiddoProcess(Useruser){// 1.根据entity构建domainEvent的dto作为防腐层UserMsgBOuserMsgBObuildMsgBO(user);// 2. 发送domainEventapplicationEventPublisher.publishEvent(userMsgBO);//3. 数据库操作}3.2 使用TransactionalEventListenerAsync(springEventExecutor)TransactionalEventListener(UserMsgBO.class)publicvoiddoSendMsg(UserMsgBOuserMsgBO){log.info(doSendMsg, msg:[{}],JSON.toJSONString(userMsgBO));try{// 具体的处理逻辑}catch(Exceptione){log.error(doSendMsg error, msg:[{}],JSON.toJSONString(userMsgBO),e);}}总结B端系统开发需要考虑事务兼顾性能没有银子弹本文只是给出了一种常见场景的常见解决方案。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进