柒柒架构:DDD领域驱动设计中的领域模型与AspectJ和Lombok冲突 -- 知识铺
《柒柒架构》DDD领域驱动设计–领域模型(三)
前言
在前两篇文章中,我们探讨了领域模型的基本概念以及仓储模型的实现。在深入了解仓储的使用之前,我们先来探讨工厂模型这一重要概念。
工厂模型
在开始学习工厂模式之前,建议先熟悉几种创建型设计模式,包括单例、原型、建造者、工厂方法和抽象工厂模式。
DDD实体在不同阶段的定义
在领域驱动设计(DDD)中,实体的定义会随着业务逻辑的深入而发生变化。了解这些变化对于设计和实现领域模型至关重要。
工厂模式
工厂模式是一种设计模式,用于创建对象,而不需要指定将要创建的对象的确切类。它有助于提高系统的灵活性和可扩展性。
DTO与DO的转换
数据传输对象(DTO)和领域对象(DO)之间的转换是实现领域层与表示层解耦的关键。
仓储的使用
仓储是领域模型中用于数据访问的组件,它封装了对聚合根的访问逻辑。
SpringAOP与AspectJ
SpringAOP和AspectJ是两种面向切面编程的实现方式,它们可以用于实现如事务管理、日志记录等横切关注点。
小结
本篇文章对DDD中的工厂模型和仓储使用进行了介绍,为读者提供了深入理解领域驱动设计的理论基础和实践应用。
由上图可以看出,在各阶段,需要对业务实体数据进行相应的转换,以保证各个层级的内部自洽性,及本层及其下层,应该不依赖上层相关类的定义。
几个概念,不同的人及书籍对DDD的几个概念描述都不尽一致,在本文中,使用如下描述:
DO:领域层对象,包含聚合、实体、值对象
Aggregate:聚合
Aggregate root:聚合根
Entity:实体
VO(value object):值对象
DTO:数据转换对象,主要用于用户接口层对外的数据传输
PO(persistent object):持久化对象
AclDto:防腐层数据传输对象
AclPO:防腐层持久化对象
QUERY:读写分离模式的外部只读请求
COMMAND: 读写分离式的外部写入请求
我们应该认清各个概念所处的层级,以及在各层级进行数据传输时,该进行怎样的数据转换。各层严格来说应该仅可使用该层的概念,比如在领域层不应该出现PO类型的对象,PO应属于基础设施层。
工厂模式
我们要将各层之间数据转换交由工厂中进行实现,通过工厂封装具体的对象转换逻辑,避免将不同层级的对象在不属于该层级的地方出现。
从上图看出,我们需要在多处创建工厂,执行不同层级的数据转换。
最简单的工厂实现如下:(例子来源网络)
public class ApproverAssembler {
public static ApproverDTO toDTO(Approver approver){
ApproverDTO dto = new ApproverDTO();
dto.setPersonId(approver.getPersonId());
dto.setPersonName(approver.getPersonName());
return dto;
}
public static Approver toDO(ApproverDTO dto){
Approver approver = new Approver();
approver.setPersonId(dto.getPersonId());
approver.setPersonName(dto.getPersonName());
return approver;
}
}
《柒柒架构》提供了通用的数据转换工具,且已经在底层实现,我们先看下如何实现的。以PO工厂为例:
仓储实现持久化时进行转换:
@Override
public void onInsert(Entity entity, Class c) {
//1.获取DO对应的Mapper对象
BaseMapper mapper = getMapper(c);
//2.转换成相应的DO
Po aPo = PoAssemblerFactory.toPo(entity);
//3.执行Mapper处理
mapper.insert(aPo);
}
我们再来看下PoAssemblerFactory的实现:
public interface PoAssemblerFactory {
static Po toPo(Entity entity) {
PoAssembleUtil poAssembleUtil = SpringContextUtil.getBean(PoAssembleUtil.class);
Po po = null;
try {
po = poAssembleUtil.toPo(entity);
} catch (Exception e) {
String poPackage = SpringContextUtil.getProperty("actable.model.pack");
String poClassName = poPackage + entity.getClass().getSimpleName() + "Po";
Class poClass = null;
try {
poClass = Class.forName(poClassName);
return (Po) PropertiesCopyUtil.deepCopyProperties(entity, poClass);
} catch (ClassNotFoundException e1) {
e.printStackTrace();
}
}
return po;
}
static Entity toEntity(Po po, Class c) {
PoAssembleUtil poAssembleUtil = SpringContextUtil.getBean(PoAssembleUtil.class);
Entity entity = null;
try {
entity = poAssembleUtil.toEntity(po, c);
} catch (Exception e) {
PropertiesCopyUtil.deepCopyProperties(po, c);
}
return entity;
}
}
可以看出,工厂类先去使用PoAssembleUtil进行转换,转换失败则会使用PropertiesCopyUtil对象进行转换。(PropertiesCopyUtil是自己定义的可以深度进行属性复制的工具类,下面贴下部分代码)
@Slf4j
public class PropertiesCopyUtil {
public static <T> T deepCopyProperties(Object source, Class<T> targetClass) {
if (Map.class.isAssignableFrom(targetClass)) {
return (T) toFlattenMap(source, null);
} else {
Map map = toFlattenMap(source, null);
return toDeepCopyProperties(map, targetClass);
}
}
@SneakyThrows
private static <T> T fromDeepCopyProperties(Object source, Class<T> targetClass) {
T target = targetClass.newInstance();
fromCopy(source, target);
return target;
}
@SneakyThrows
private static <T> T toDeepCopyProperties(Object source, Class<T> targetClass) {
T target = targetClass.newInstance();
toCopy(source, target);
return target;
}
}
下面我们看下PoAssembleUtil代码:
public class PoAssembleUtil {
private Map<String, Method> methodMap = new HashMap<>();
@SneakyThrows
public Po toPo(Entity o) {
Class c = o.getClass();
Object poAssemble = getPoAssemble(c);
if (poAssemble == null) {
throw new Exception("未自定义" + c.getSimpleName() + "PO转化类!");
}
if (poAssemble instanceof PoAssemble) {
return ((PoAssemble) poAssemble).toPo(o);
} else {
Method toDoMethod = methodMap.get(c.getSimpleName() + "toPo");
if (toDoMethod == null) {
Method[] methods = poAssemble.getClass().getMethods();
for (Method m : methods) {
if (m.getName().equals("toPo")) {
methodMap.put(c.getSimpleName() + "toPo", m);
toDoMethod = m;
break;
}
}
}
Object value = toDoMethod.invoke(poAssemble, o);
return (Po) value;
}
}
@SneakyThrows
public Entity toEntity(Po o, Class c) {
Object poAssemble = getPoAssemble(c);
if (poAssemble == null) {
throw new Exception("未自定义" + c.getSimpleName() + "PO转化类!");
}
if (poAssemble instanceof PoAssemble) {
return ((PoAssemble) poAssemble).toEntity(o);
} else {
Method toDoMethod = methodMap.get(c.getSimpleName() + "toEntity");
if (toDoMethod == null) {
Method[] methods = poAssemble.getClass().getMethods();
for (Method m : methods) {
if (m.getName().equals("toEntity")) {
methodMap.put(c.getSimpleName() + "toEntity", m);
toDoMethod = m;
break;
}
}
}
Object value = toDoMethod.invoke(poAssemble, o);
return (Entity) value;
}
}
private Object getPoAssemble(Class c) {
String className = c.getSimpleName();
String mapperName = className.substring(0, 1).toLowerCase().concat(className.substring(1)) + "PoAssemblerImpl";
return SpringContextUtil.getBean(mapperName);
}
}
该工具类会先去Spring容器中查找有无名为“${实体ID}PoAssemblerImpl”的bean,如果找到的话,则会调用该bean对应的toPo或者toEntity方法。
这边建议使用MapStruct工具对不同类型的对象进行转换,因为Mapstruct在转换上相对于其他工具有着更高的效率,且可以通过注解完成栏位的映射配置。如果字段一一对应,可无需进行Mapping映射。
下面是使用Mapstruct进行转换的例子:
@Mapper(componentModel = "spring") //使用通用处理仓库时,必须使用spring管理
// 注意这里,在使用通用处理仓库对象处理时,必须继承DomainDoAssemble接口
public interface CustomInfoPoAssembler {
//引用静态方式时,必须要要有此属性
//使用spring注入时,可以不需要。使用通用仓库对象时,必须使用spring管理,这个可以不需要
CustomInfoPoAssembler INSTANCES = Mappers.getMapper(CustomInfoPoAssembler.class);
@Mappings({@Mapping(target = "userId", source = "user.userId"), @Mapping(target = "userName", source = "user.userName"),
@Mapping(target = "positionId", source = "position.positionId"), @Mapping(target = "positionName", source = "position.positionName")})
CustomInfoPo toPo(CustomInfo customInfo);
// 如果字段没有不一致,一行注解都不需要
@Mappings({@Mapping(source = "userId", target = "user.userId"), @Mapping(source = "userName", target = "user.userName"),
@Mapping(source = "positionId", target = "position.positionId"), @Mapping(source = "positionName", target = "position.positionName")})
CustomInfo toEntity(CustomInfoPo customInfoDo);
}
DTO与DO转换
在软件开发中,DTO(Data Transfer Object)和DO(Domain Object)是两种常见的对象类型。DTO通常用于在用户接口层与应用层之间传输数据,而DO则代表领域模型对象。当数据从用户界面或第三方应用流入应用层时,需要将DTO转换为DO对象。由于存在多种DTO类型,转换过程可能会相对复杂。以下是两种转换方式的建议:
- 手动转换:开发者可以根据DTO和DO的结构,手动编写转换逻辑。2. 使用工具类:可以利用《柒柒架构》提供的
PropertiesCopyUtil
工具类来简化对象转换过程。
工厂模式与仓储模式
在介绍仓储模式之前,我们先回顾一下工厂模式。工厂模式是一种创建型设计模式,用于创建对象,而不需要指定将要创建的对象的具体类。接下来,我们将探讨仓储模式的应用。
仓储模式
仓储模式是一种设计模式,用于在应用程序中提供数据访问对象(Repository)的抽象,允许开发者以一种统一的方式访问数据源。
Spring AOP与AspectJ
在深入仓储模式之前,我们先来了解Spring AOP(面向切面编程)和AspectJ。Spring AOP在Spring Boot应用启动时,对bean进行动态代理,主要特点如下:
- Spring管理的对象:只有那些交由Spring管理的对象才能被动态代理。2. 不适合代理的对象:有些对象,如在线程中使用后立即销毁的实体对象,不适合由Spring管理。3. 聚合对象:聚合对象通常属于不适合由Spring管理的范畴。
AspectJ代理
AspectJ是一个面向切面的编程框架,它允许开发者以声明方式添加额外功能,如日志记录、事务管理等,而不需要修改业务逻辑代码。
结语
通过上述内容,我们了解了DTO与DO的转换方法,工厂模式和仓储模式的概念,以及Spring AOP和AspectJ的基本知识。这些知识点对于构建高效、可维护的应用程序至关重要。
public @interface LogAnnotation {
String value() default "INFO";
}
@Aspect
@Component
public class LogAop {
private final String strDateFormat = "yyyy-MM-dd HH:mm:ss.SSSSSS";
private final SimpleDateFormat sdf = new SimpleDateFormat(strDateFormat);
@Pointcut("@annotation(com.cmbchina.ns.casesystem.common.infrastructure.utils.logaop.LogAnnotation)")
public void logAspect() {
}
@Around("logAspect()")
public Object infoLog(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object[] querys = joinPoint.getArgs();
String queryStr = Arrays.stream(querys).map(x -> x.toString()).reduce(new BinaryOperator<String>() {
@Override
public String apply(String s, String s2) {
return s + "," + s2;
}
}).get();
log.info("开始执行时间:" + sdf.format(startTime) + ",请求参数:" + queryStr + "。");
Object o = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
log.info("结束执行时间:" + sdf.format(endTime) + ",执行时长:" + duration + ",返回值:" + Optional.ofNullable(o).toString() + "。");
return o;
} catch (Exception t) {
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
log.error("执行异常时间:" + sdf.format(endTime) + ",执行时长:" + duration + ",错误原因:" + t.getMessage() + "。");
t.printStackTrace();
throw t;
}
}
}
在Java开发中,动态代理是一种常用的技术,Spring框架和Spring Boot都提供了对动态代理的支持。以下是对动态代理技术及其在Spring AOP和AspectJ中的实现方式的详细解析:
动态代理概述动态代理是一种在运行时动态创建代理对象的技术,它允许开发者在不修改原有代码的情况下,增加额外的功能。
Spring AOP的动态代理Spring AOP使用的是代理模式,通过创建目标对象的代理来实现AOP的功能。Spring AOP的代理是基于接口的,因此目标对象必须实现一个接口。
AspectJ的动态代理与Spring AOP不同,AspectJ使用的是字节码操作技术,它在编译时对字节码进行增强。AspectJ的代码没有Spring框架的约束,可以独立于Spring使用。
AspectJ的配置要在项目中使用AspectJ,需要在pom.xml
文件中添加AspectJ的相关依赖,在编译时指定AspectJ的配置文件。
代码实现具体的实现代码展示了如何使用AspectJ注解来定义切面和切点。
注意事项- AspectJ和Lombok可能会存在冲突。当使用AspectJ时,IDEA默认使用Ajc进行编译,但可以通过设置更改为使用javac编译来解决这个问题。
结论动态代理是Java开发中一个强大的功能,Spring AOP和AspectJ提供了两种不同的实现方式,开发者可以根据项目需求选择适合的实现。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.11</version>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<complianceLevel>${java.version}</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<artifactId>common</artifactId>
<groupId>{这里是你的工程}</groupId>
</aspectLibrary>
</aspectLibraries>
<aspectDirectory>
{这里是你的切面对象}
</aspectDirectory>
<forceAjcCompile>true</forceAjcCompile>
<sources/>
<weaveDirectories>
<weaveDirectory>${project.build.outputDirectory}</weaveDirectory>
</weaveDirectories>
</configuration>
</plugin>
由于我们的DO聚合对象都是被new出来的,是不适合被Spring管理的,所以我们选择使用AspectJ进行动态代理,我们需要将聚合对象无感知的进行仓储方法的执行。
那先看下,我们是如何对其进行代理的。
package com.cmbchina.ns.casesystem.common.domain.aspectjaop;
import com.cmbchina.ns.casesystem.common.domain.Aggregate;
import com.cmbchina.ns.casesystem.common.domain.Application;
import com.cmbchina.ns.casesystem.common.domain.repository.BaseRepository;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
/***
* @author by zhoufankun
* @date 2021/07/26
*/
@Aspect
@Slf4j
public class AggregateEntityAspectjAop {
@Pointcut("execution(* com.cmbchina.ns.casesystem.common.domain.Aggregate+.*(..)) " +
"&& !execution(* get*(..)) && !execution(* toString(..)) " +
"&& !execution(* hashCode(..)) && !execution(* canEqual(..)) && !execution(* equals(..))" +
"&& !execution(* builder(..)) && !execution(* idValue(..)) && !execution(* idKey(..))")
public void aggregateAspect() {
}
@SneakyThrows
@Around("aggregateAspect()")
public Object aroundAggregate(ProceedingJoinPoint joinPoint) {
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
Object o = null;
if (stackTraceElement.getClassName().startsWith("sun.reflect.GeneratedMethodAccessor")) {
return joinPoint.proceed();
}
if (Application.class.isAssignableFrom(Class.forName(stackTraceElement.getClassName()))) {
o = joinPoint.proceed();
Aggregate aggregate = (Aggregate) joinPoint.getTarget();
Class c = aggregate.getClass();
BaseRepository.save(aggregate, c);
} else {
o = joinPoint.proceed();
}
return o;
}
@Pointcut("execution(* com.cmbchina.ns.casesystem.common.domain.Aggregate+.*(..)) " +
"&& execution(* get*(..)) ")
public void aggregateGetMethodAspect() {
}
@SneakyThrows
@Around("aggregateGetMethodAspect()")
public Object aroundAggregateGetMethod(ProceedingJoinPoint joinPoint) {
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
Object o = null;
if (stackTraceElement.getClassName().startsWith("sun.reflect.GeneratedMethodAccessor")) {
return joinPoint.proceed();
}
if (Application.class.isAssignableFrom(Class.forName(stackTraceElement.getClassName()))) {
o = joinPoint.proceed();
if (Aggregate.class.isAssignableFrom(o.getClass())) {
long threadId = Thread.currentThread().getId();
Aggregate aggregate = (Aggregate) joinPoint.getTarget();
Class c = aggregate.getClass();
ThreadLocalAggregateAopUtil.setAggregate(threadId, aggregate);
ThreadLocalAggregateAopUtil.setAggregateClass(threadId, c);
}
} else {
o = joinPoint.proceed();
}
return o;
}
@Pointcut("execution(* com.cmbchina.ns.casesystem.common.domain.Entity+.*(..)) " +
"&& !execution(* get*(..)) && !execution(* toString(..)) " +
"&& !execution(* hashCode(..)) && !execution(* canEqual(..)) && !execution(* equals(..))" +
"&& !execution(* builder(..)) ")
public void entityAspect() {
}
@SneakyThrows
@Around("entityAspect()")
public Object aroundEntity(ProceedingJoinPoint joinPoint) {
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
Object o = null;
if (stackTraceElement.getClassName().startsWith("sun.reflect.GeneratedMethodAccessor")) {
return joinPoint.proceed();
}
if (Application.class.isAssignableFrom(Class.forName(stackTraceElement.getClassName()))) {
long threadId = Thread.currentThread().getId();
o = joinPoint.proceed();
Aggregate aggregate = ThreadLocalAggregateAopUtil.getAggregate(threadId);
Class c = ThreadLocalAggregateAopUtil.getAggregateClass(threadId);
BaseRepository.save(aggregate, c);
} else {
o = joinPoint.proceed();
}
return o;
}
}
在Spring框架中,Lombok是一个常用的库,它可以通过注解自动生成getters、setters等方法,从而简化代码。然而,我们通常希望对某些方法进行代理,而排除Lombok生成的方法。以下是对上述需求的详细解析:
- 方法代理规则:我们希望排除Lombok生成的方法和以get开头的方法,不对这些方法进行代理。
- 调用检查:我们需要检查对聚合或实体的调用是否源自Application对象。如果是,我们通过调用仓储(Repository)的方法来进行保存操作;如果不是,则不进行任何处理。
- 事务管理:从Application对象发起的调用将触发事务操作,而聚合内部的相互调用则不会启动事务。
- 简化操作:在聚合和实体的业务方法中,开发者无需显式定义
BaseRepository.save()
方法,即可完成仓储操作。 接下来,我们看看BaseRepository
的实现细节。
package com.cmbchina.ns.casesystem.common.domain.repository;
import com.cmbchina.ns.casesystem.common.domain.Aggregate;
import com.cmbchina.ns.casesystem.common.domain.busivalid.DddValidationManager;
import com.cmbchina.ns.casesystem.common.infrastructure.utils.SpringContextUtil;
import lombok.SneakyThrows;
import java.util.List;
/***
* 继承该抽象类,可以实现自动获取对应Aggregate对象对应的仓储对象。
* 仓储对象命名规范: “{aggregate}RepositoryImpl”
* @auther by zhoufankun
* @date 2021/7/30
*/
public interface BaseRepository {
/**
* @param id 传入的聚合根ID
* @param c 传入的聚合class类型
* @return 返回查到的聚合结果
*/
@SneakyThrows
static <T extends Aggregate> T find(Object id, Class<T> c) {
String className = c.getSimpleName();
Repository repository = (Repository) SpringContextUtil.getBean(className.substring(0, 1).toLowerCase().concat(className.substring(1)) + "RepositoryImpl");
if (repository == null) {
repository = (Repository) SpringContextUtil.getBean("generalRepositoryImpl");
}
return (T) repository.find(id, c);
}
/**
* @param id 传入的聚合根ID
* @param c 传入的聚合class类型
*/
@SneakyThrows
static <T extends Aggregate> void remove(Object id, Class<T> c) {
String className = c.getSimpleName();
Repository repository = (Repository) SpringContextUtil.getBean(className.substring(0, 1).toLowerCase().concat(className.substring(1)) + "RepositoryImpl");
if (repository == null) {
repository = (Repository) SpringContextUtil.getBean("generalRepositoryImpl");
}
repository.remove(id, c);
}
/**
* @param aggregate,c 传入的聚合class类型
*/
@SneakyThrows
static <T extends Aggregate> void save(Aggregate aggregate, Class<T> c) {
DddValidationManager dddValidationManager = SpringContextUtil.getBean(DddValidationManager.class);
List<DddValidationManager.ValidResult> valid = dddValidationManager.valid(aggregate);
if (valid == null || valid.size() == 0) {
String className = c.getSimpleName();
Repository repository = (Repository) SpringContextUtil.getBean(className.substring(0, 1).toLowerCase().concat(className.substring(1)) + "RepositoryImpl");
if (repository == null) {
repository = (Repository) SpringContextUtil.getBean("generalRepositoryImpl");
}
repository.save(aggregate, c);
} else {
throw new Exception(valid.toString());
}
}
}
我们看出,BaseRepository是我们操作仓储的基础工具,无需显示使用其他仓储实现类。
- 如果应用中有定义“${聚合ID}RepositoryImpl”,我们则会使用该实现进行仓储的处理,如果没有显示定义,我们则会使用通用的generalRepositoryImpl bean进行仓储操作,一般情况下,使用通用仓储即可满足。
我们看看具体使用:
@Service
public class CustomInfoApplication implements Application {
@Autowired
private CustomInfoService customInfoService;
public CustomInfo query(String id) {
return BaseRepository.find(id, CustomInfo.class);
}
public void insert(CustomInfo customInfo) {
BaseRepository.save(customInfo, CustomInfo.class);
}
public void delete(String id) {
BaseRepository.remove(id, CustomInfo.class);
}
public void update(CustomInfo customInfo) {
BaseRepository.save(customInfo, CustomInfo.class);
}
@EventDrivenPattern("transCash.success")
public void doSomething(String id) {
CustomInfo customInfo = BaseRepository.find(id, CustomInfo.class);
customInfo.handle1();
}
public void transCash(int cashMoney, String id1, String id2) {
CustomInfo customInfo1 = BaseRepository.find(id1, CustomInfo.class);
CustomInfo customInfo2 = BaseRepository.find(id2, CustomInfo.class);
customInfoService.transCash(cashMoney, customInfo1, customInfo2);
}
}
在大多数情况下,除了插入和删除操作需要显示地使用BaseRepository进行处理之外,其他相关业务操作用户无需显式执行。这是因为在没有聚合对象的情况下,应用程序不可能直接对聚合本身进行操作,因此获取聚合对象必须通过BaseRepository类来实现。用户在完成业务操作后,代理对象会自动执行BaseRepository.save()方法。 通过查看代理对象的实现,我们可以发现其具体细节。
StackTraceElement stackTraceElement = Thread.currentThread().getStackTrace()[3];
if (Application.class.isAssignableFrom(Class.forName(stackTraceElement.getClassName()))) {
xxxxxx
}
在软件开发中,事务处理是一个关键的概念,特别是在领域驱动设计(Domain-Driven Design, DDD)中。事务确保了业务操作的完整性和一致性。以下是对事务处理和领域层仓储模型的详细阐述:
事务处理的重要性事务处理是确保数据一致性的重要机制。在DDD中,只有当从应用层调用聚合或实体的业务方法时,才会触发事务。这意味着,如果聚合内部互相调用,不会触发新的事务,从而保证了业务操作的事务性。
领域层仓储模型领域层的仓储模型是DDD架构中的核心组成部分。它负责存储和检索聚合根。通过使用仓储模型,可以简化数据访问逻辑,使开发者能够更专注于业务逻辑的实现。
低代码实现《柒柒架构》在实现低代码开发方面做出了贡献。它通过减少架构使用者需要编写的底层代码,使得开发者可以更加专注于业务逻辑,从而提高开发效率。
仓储模型的实现与使用理解仓储模型的实现和使用,对于开发者来说非常关键。即使不采用DDD思想,掌握仓储模型也能带来诸多好处,比如简化数据访问和提高代码的可维护性。
小结本文对领域层的仓储模型进行了深入探讨,揭示了其在DDD架构中的核心地位以及如何通过《柒柒架构》实现低代码开发,从而减少底层代码的开发工作。
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/geek001/post/20240730/%E6%9F%92%E6%9F%92%E6%9E%B6%E6%9E%84DDD%E9%A2%86%E5%9F%9F%E9%A9%B1%E5%8A%A8%E8%AE%BE%E8%AE%A1--%E9%A2%86%E5%9F%9F%E6%A8%A1%E5%9E%8B%E4%B8%89_aspectj-%E5%92%8Clombok%E5%86%B2%E7%AA%81--%E7%9F%A5%E8%AF%86%E9%93%BA/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com