DDD架构下的分层以及@Transactional事务与Spring事件机制使用
一、背景
项目以DDD结构构建,多个实体领域,对于单个实体领域的domainService修改,写入方法都加了@Transactional事务,所以调用中很多使用到了嵌套事务,且为了做信息数据同步到其他服务和商户日志,这些专门放到事件机制中做。
一千开发对DDD模型实现有一千种方法。
一般DDD模型下:
- api层(外部接口) --> application(应用层主要是api的实现类) --> domain层(领域层) --> repository(存储层)
common工具层
现实使用情况下,api总是做着大量的参数校验和对多个domain的调用,以及对result参数的转换;所以容易造成大量逻辑代码的堆积,从而违背api不考虑业务逻辑原则,只需要考虑对domain的调用;所以在application层多加了一个innerService包,主要是对参数的校验,domian调用,参数转换处理。
所以改造为:
api层(外部接口) --> application(应用层apiImpl --> innerservice) --> domain层(领域层) --> repository(存储层)
common工具层
二、场景:
商户入驻牵扯到很多信息写入数据库:
- 商户信息
- 商户联系人信息
- 商户银行账户信息
- 商户日志
- 商户账号关联关系
- 商户认证逻辑
三、代码逻辑
3.1 api层:
public interface MerchantEntryFacade {
ServiceResult<CreateMerchantResponse> merchantEntry(MerchantEntryRequest request);
ServiceResult updateMerchantEntry(UpdateMerchantEntryRequest request);
}
3.2application层:
//MerchantEntryFacadeImpl
public class MerchantEntryFacadeImpl implements MerchantEntryFacade {
@Autowired
MerchantInnerService merchantInnerService;
@Autowired
AccountEnterpriseRelationDomainService accountEnterpriseRelationDomainService;
@Override
@MerchantLogin //自定义登录token校验注解aop
public ServiceResult<CreateMerchantResponse> merchantEntry(MerchantEntryRequest request){
MerchantEntryChecker.check(request);
Long guuid = merchantInnerService.merchantEntry(request);
return ServiceResultUtil.success(guuid);
}
@Override
@MerchantLogin
public ServiceResult updateMerchantEntry(UpdateMerchantEntryRequest request){
MerchantEntryChecker.check(request);
if (accountEnterpriseRelationDomainService.getRelation(request.get_loginUser().getAccountId(),request.getGuuid()) == null){
return ServiceResultUtil.fail(ErrorCodeEnum.RELATION_NONE_EXISTS);
}
Integer merchantAuthStatus = merchantInnerService.updateMerchantEntry(request);
UpdateMerchantEntryResponse response = new UpdateMerchantEntryResponse();
response.setAuthStatus(merchantAuthStatus);
return ServiceResultUtil.success(response);
}
}
//MerchantInnerService
@Transactional
public Long merchantEntry(MerchantEntryRequest request){
RedisLock redisLock = redisClientService.createLock(RedisKey.LOCK_MERCHANT);
try {
if (redisLock.blockAcquireLock(12000, 12000)) {
//商户其他认证,校验
Merchant merchant = merchantDomainService.create(merchantEntryContext,false,false);
//构建商户日志
MerchantOperationLog merchantOperationLog = MerchantOperationLogFactory.generateLog(AccountOperationMoudleEnum.MERCHANT_RECORD.getType(),merchant.getGuid(), OperationTypeEnum.Add.getType(),"自主入驻:"+merchant.getMerchantName());
//商户信息变更,事件机制
SpringContextHolder.publish(new MerchantEvent<Merchant>("MERCHANT", merchant,merchantOperationLog));
}
} finally {
redisLock.releaseLock();
}
return merchant.getGuuid();
}
3.2.1事件处理
@Component
@Slf4j
public class MerchantEventListener {
@Autowired
MerchantAdminClient merchantAdminClient;
@Autowired
MerchantOperationLogRepository merchantOperationLogRepository;
@EventListener
public void merchantBusinessRecordEventListener(MerchantEvent<Merchant> event) {
Merchant merchant = event.getData();
MerchantOperationLog merchantOperationLog = event.getMerchantOperationLog();
try {
if (merchant == null) {
return;
}
if (merchant.getMerchantStatus().equals(MerchantStatusEnum.ENABLED.getType())){
merchantAdminClient.sendMessage(merchant.getGuuid());
}
if (merchantOperationLog != null) {
merchantOperationLogRepository.insert(merchantOperationLog);
}
} catch (Exception e) {
log.error("商户档案变更,同步发送消息失败", e);
}
}
}
3.3 domain业务领域层
这里子事务指定Propagation.REQUIRES_NEW,开启一个新的事务,不受外层事务影响。
如果@Transactional嵌套@Transactional,默认事物按照父事物提交时间提交,这个会造成application层同步商户信息,发送事件,事件发送出去插入数据事务还未提交。
//MerchantDomainService
//
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Merchant create(MerchantEntryContext merchantEntry , boolean createBusinessRecord,boolean defaultEnterprise){
//业务逻辑处理,写入数据
XXXRepository.create(entity);
return merchant;
}
3.4 Repository存储层
//MerchantRepository
public interface MerchantRepository {
Long generateId();
Merchant create(Merchant merchant);
}
public class MerchantRepositoryImpl implements MerchantRepository {
@Autowired
MerchantMapper merchantMapper;
@Override
public Long generateId() {
return merchantMapper.nextGuuid();
}
@Override
public Merchant create(Merchant merchant) {
if (StringUtils.isEmpty(merchant.getGuid())) {
merchant.setGuid(ModelUtil.uuid());
}
if ( merchant.getGuuid() == null){
merchant.setGuuid(merchantMapper.nextGuuid());
}
merchant.setMerchantCode(getMerchantCode());
merchantMapper.insert(MerchantConverter.convert2PO(merchant));
return merchant;
}
}
3.3.1定义事件
@Data
public class MerchantEvent<T> extends ApplicationEvent {
private T data;
private MerchantOperationLog merchantOperationLog;
public MerchantEvent(Object source) {
super(source);
}
public MerchantEvent(Object source,T data,MerchantOperationLog operationLog){
super(source);
this.data = data;
this.merchantOperationLog = operationLog;
}
}
3.3.2 事件机制
@Component
public class SpringContextHolder implements ApplicationContextAware {
private static ApplicationContext applicationContext = null;
/**
* 获取静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
assertContextInjected();
return applicationContext;
}
/**
* 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) {
assertContextInjected();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
*/
public static <T> T getBean(Class<T> requiredType) {
assertContextInjected();
return applicationContext.getBean(requiredType);
}
/**
* 清除SpringContextHolder中的ApplicationContext为Null.
*/
public static void clearHolder() {
applicationContext = null;
}
/**
* 实现ApplicationContextAware接口, 注入Context到静态变量中.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext;
}
/**
* 检查ApplicationContext不为空.
*/
private static void assertContextInjected() {
Validate.validState(applicationContext != null,
"applicaitonContext属性未注入, 请在applicationContext.xml中定义SpringContextHolder.");
}
public static void publish(Object event) {
applicationContext.publishEvent(event);
}
}
四、事件
1、事件触发 && 监听处理过程
(1) 使用 org.springframework.context 包下的 ApplicationContext.publishEvent(ApplicationEvent appEvent) 发布事件
(2) 使用 org.springframework.context.event 包下的 @EventListener(事件名) 监听事件并处理。
2、注意点
(1) ApplicationContext.publishEvent 默认是同步操作, 并非发布后不管的异步操作,发布事件后需要等 @EventListener 执行完
(2) 如果需要开启异步操作 需要在 @EventListener 上 增加 @Async 注解。
4.1事件机制可查看:
https://blog.csdn.net/weixin_45405425/article/details/120845745?spm=1001.2014.3001.5502
五、@Transactional
5.1概述
spring事务的原理是什么?
首先mysql这样的数据库本身是支持事务的,有不同的事务隔离级别,事务分为手动开启事务和自动开启事务,参见 【mysql】MYSQL事务的开启与提交命令答疑,通过底层的支持,可以实现多条sql 原子化,要么都执行,要么都不执行
spring事务采用注解生成代理对象,把默认的自动开启事务变为手动开启,这样 多条sql语句都执行完后,才会提交事务
- 什么是spring的事务
Spring事务 的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:
事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。
1. 获取连接 Connection con = DriverManager.getConnection()
2. 开启事务con.setAutoCommit(true/false);
3. 执行CRUD
4. 提交事务/回滚事务 con.commit() / con.rollback();
5. 关闭连接 conn.close();
使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。
那么Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了。下面简单地介绍下,注解方式为例子:
配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
spring事务的实现可以当做是springAOP和IOC的结合使用。设计原理就是使用AOP实现了声明式事务处理的interceptor,封装了对spring的处理过程。
真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
- 两种使用事务的方式
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是声明式的,
@Transactional注解就是声明式的。
显然声明式事务管理要优于编程式事务管理,这正是Spring倡导的非侵入式的编程方式。唯一不足的地方就是声明式事务管理的粒度是方法级别,而编程式事务管理是可以到代码块的,但是可以通过提取方法的方式完成声明式事务管理的配置。
5.2 编程式事务管理
编程式事务管理是侵入性事务管理,使用TransactionTemplate或者直接使用PlatformTransactionManager,对于编程式事务管理,Spring推荐使用TransactionTemplate。
5.3 声明式事务管理
声明式事务也分为多种具体的种类,比如xml配置、注解元数据驱动(@Transactional注解)
声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
编程式事务每次实现都要单独实现,但业务量大功能复杂时,使用编程式事务无疑是痛苦的,而声明式事务不同,声明式事务属于无侵入式,不会影响业务逻辑的实现,只需要在配置文件中做相关的事务规则声明或者通过注解的方式,便可以将事务规则应用到业务逻辑中。
5.4 事务的原理
spring事务的实现可以当做是springAOP和IOC的结合使用。设计原理就是使用AOP实现了声明式事务处理的interceptor,封装了对spring的处理过程。
声明式事务实现首先需要配置aop环境,生成代理对象TransactionProxy和TrasactionInterceptor。封装不同的事务处理,整合具体实现到AOP和IOC。提供了即开即用的事务服务功能。在事务处理过程中,TransactionInfo和TransactionStatus是事务处理信息和状态的数据存储的对象。后面的事务处理都需要根据状态来进行的,当做参数传入。具体的事务处理是交给TransactionManager管理的,他提供了通用的事务处理模板,如doCommit等。具体的操作由具体的事务处理器来实现。
在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。
当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。
5.5 Spring 事务的传播属性
事务的传播性一般用在事务嵌套的场景,比如一个事务方法里面调用了另外一个事务方法,那么两个方法是各自作为独立的方法提交还是内层的事务合并到外层的事务一起提交,这就是需要事务传播机制的配置来确定怎么样执行。
这些属性在TransactionDefinition中定义,常用的事务传播机制如下:
PROPAGATION_REQUIRED
Spring默认的传播机制,能满足绝大部分业务需求,如果外层有事务,则当前事务加入到外层事务,一块提交,一块回滚。如果外层没有事务,新建一个事务执行
PROPAGATION_REQUES_NEW
该事务传播机制是每次都会新开启一个事务,同时把外层事务挂起,当当前事务执行完毕,恢复上层事务的执行。如果外层没有事务,执行当前新开启的事务即可
PROPAGATION_SUPPORT
如果外层有事务,则加入外层事务,如果外层没有事务,则直接使用非事务方式执行。完全依赖外层的事务
PROPAGATION_NOT_SUPPORT
该传播机制不支持事务,如果外层存在事务则挂起,执行完当前代码,则恢复外层事务,无论是否异常都不会回滚当前的代码
PROPAGATION_NEVER
该传播机制不支持外层事务,即如果外层有事务就抛出异常
PROPAGATION_MANDATORY
与NEVER相反,如果外层没有事务,则抛出异常
PROPAGATION_NESTED
嵌套事务呈现父子事务概念,二者之间是有关联的,核心思想就是子事务不会独立提交,而是取决于父事务,当父事务提交,那么子事务才会随之提交;如果父事务回滚,那么子事务也回滚。
但是子事务又有自己的特性,那 就是可以独立进行回滚,不会引发父事务整体的回滚(当然需要try catch子事务,避免异常传递至父层事务,如果没有,则也会引发腐父事务整体回滚)。这个特性比较有意思,虽然不能独立提交,但是可以独立回滚,因此,如果存在ABC 三个子事务,那么每个子事务都可以独立回滚,子事务类似一个游戏中的保存点,假设某个时间点,创建了一个保存点A,角色有10发子弹,主线继续发生时,对应执行某个子事务内的逻辑,如果游戏角色打了4发子弹,剩余6发子弹时挂了,点击返回上一个保存点,可以重新玩一次,此时该角色又是10发子弹,对应的就是子事务发生异常,子事务回滚到事务执行之前的那个点,放佛从来没有执行过该子事务一样,数据库的数据也不会发生变更。
该传播机制的特点是可以保存状态保存点,当前事务回滚到某一个点,从而避免所有的嵌套事务都回滚,即各自回滚各自的,如果子事务没有把异常吃掉,基本还是会引起全部回滚的。
传播规则回答了这样一个问题:一个新的事务应该被启动还是被挂起,或者是一个方法是否应该在事务性上下文中运行。
常用的:
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_NESTED
5.7 什么是默认事务传播类型
要注意默认事务传播类型和未设置事务的区别。
默认事务传播的前提是指通过配置或编码,引入事务,如果没有指定事务的传播类型,此时默认的事务传播类型为PROPAGATION_REQUIRED。
而未设置事务,就是没有事务。