Java 如何正确地将域实体转换为DTO,同时考虑可扩展性&;可测试性
我已经阅读了几篇关于将域对象转换为DTO的文章和Stackoverflow文章,并在我的代码中试用了它们。说到测试和可伸缩性,我总是面临一些问题。我知道以下三种将域对象转换为DTO的可能解决方案。大部分时间我都在使用Spring 解决方案1:服务层中用于转换的私有方法 第一种可能的解决方案是在服务层代码中创建一个小的“helper”方法,将检索到的数据库对象转换为我的DTO对象Java 如何正确地将域实体转换为DTO,同时考虑可扩展性&;可测试性,java,spring,spring-boot,design-patterns,junit,Java,Spring,Spring Boot,Design Patterns,Junit,我已经阅读了几篇关于将域对象转换为DTO的文章和Stackoverflow文章,并在我的代码中试用了它们。说到测试和可伸缩性,我总是面临一些问题。我知道以下三种将域对象转换为DTO的可能解决方案。大部分时间我都在使用Spring 解决方案1:服务层中用于转换的私有方法 第一种可能的解决方案是在服务层代码中创建一个小的“helper”方法,将检索到的数据库对象转换为我的DTO对象 @Service public MyEntityService { public SomeDto getEnti
@Service
public MyEntityService {
public SomeDto getEntityById(Long id){
SomeEntity dbResult = someDao.findById(id);
SomeDto dtoResult = convert(dbResult);
// ... more logic happens
return dtoResult;
}
public SomeDto convert(SomeEntity entity){
//... Object creation and using getter/setter for converting
}
}
优点:
- 易于实现
- 不需要额外的转换类->项目不需要实体
- 测试时出现问题,因为私有方法中使用了
,如果对象嵌套很深,我必须在(someDao.findById(id))时提供适当的new SomeEntity()
,以避免在转换同时溶解嵌套结构时出现空指针结果。然后返回(alsodeplynestedobject)
public class SomeDto {
// ... some attributes
public SomeDto(SomeEntity entity) {
this.attribute = entity.getAttribute();
// ... nesting convertion & convertion of lists and arrays
}
}
优点:
- 不需要额外的转换类
- 隐藏在DTO实体->服务代码中的转换更小
- 在服务代码中使用
,因此,由于我的new SomeDto()
模拟,我必须提供正确的嵌套对象结构李>someDao
Converter
,但是这个解决方案代表了进行转换的每个外部化类。使用这个解决方案,我将转换器注入到我的服务代码中,当我想要将域实体转换为DTO时,我调用它
优点:
- 易于测试,因为我可以在测试用例期间模拟结果
- 任务分离->一个专门的类正在做这项工作
- 不会随着我的域模型的增长而“扩展”。对于很多实体,我必须为每个新实体创建两个转换器(->将DTO转换为entitiy和entitiy转换为DTO)
提前谢谢 在我看来,第三种解决方案是最好的。是的,对于每个实体,您必须创建两个新的convert类,但是当您进行测试时,您不会有很多麻烦。您永远不应该选择这样的解决方案,它会导致您在开始时编写更少的代码,然后在测试和维护代码时编写更多的代码 解决方案1:服务层中用于转换的私有方法 我想解决方案1不会很好地工作,因为您的DTO是面向域的,而不是面向服务的。因此,它们很可能用于不同的服务中。因此,映射方法不属于一个服务,因此不应在一个服务中实现。您将如何在另一个服务中重用映射方法 1。若您对每个服务方法使用专用的DTO,那个么该解决方案将运行良好。但在最后,我们将介绍更多 解决方案2:DTO中的附加构造函数,用于将域实体转换为DTO 一般来说,这是一个不错的选择,因为您可以将DTO视为实体的适配器。换句话说:DTO是实体的另一种表示形式。这样的设计通常包装源对象,并提供方法,为您提供包装对象的另一个视图 但DTO是一个数据传输对象,因此它迟早会被序列化并通过网络发送,例如使用。在这种情况下,接收此DTO的客户端必须对其进行反序列化,因此需要其类路径中的实体类,即使它只使用DTO的接口 解决方案3:使用Spring的转换器或任何其他外部化Bean进行此转换 解决方案3是我也更喜欢的解决方案。但是我会创建一个
Mapper
接口,负责从源到目标的映射,反之亦然。例如
public interface Mapper<S,T> {
public T map(S source);
public S map(T target);
}
公共接口映射器{
公共T图(S来源);
公众地图(T目标);
}
实现可以使用映射框架完成,如
您还说每个实体都有一个转换器 不会随着我的域模型的增长而“扩展”。对于很多实体,我必须为每个新实体创建两个转换器(->将DTO转换为entitiy和entitiy转换为DTO) 我认为您只需要为一个DTO创建两个转换器或一个映射器,因为您的DTO是面向域的 一旦您开始在另一个服务中使用它,您就会认识到另一个服务通常应该或不能返回第一个服务所返回的所有值。 您将开始为每个其他服务实现另一个映射器或转换器 如果我从专用或共享DTO的优缺点开始,这个答案会很长,所以我只能请你阅读我的博客 编辑
关于第三个解决方案:你更喜欢在哪里调用映射器 在用例上方的层中。DTO是数据传输对象,因为它们将数据打包在最适合传输协议的数据结构中。因此,我将该层称为传输层。 该层负责将用例的请求和结果对象从传输表示映射到传输表示,例如json数据结构
interface
+-----+ implements || +------------+ uses +--------+
| DTO | ---------------||-> | EntityData | <---- | Entity |
+-----+ || +------------+ +--------+
public abstract class BaseEntity implements Serializable {
}
public class AbstractDto {
}
public interface GenericConverter<D extends AbstractDto, E extends BaseEntity> {
E createFrom(D dto);
D createFrom(E entity);
E updateEntity(E entity, D dto);
default List<D> createFromEntities(final Collection<E> entities) {
return entities.stream()
.map(this::createFrom)
.collect(Collectors.toList());
}
default List<E> createFromDtos(final Collection<D> dtos) {
return dtos.stream()
.map(this::createFrom)
.collect(Collectors.toList());
}
}
public interface CommentConverter extends GenericConverter<CommentDto, CommentEntity> {
}
@Component
public class CommentConverterImpl implements CommentConverter {
@Override
public CommentEntity createFrom(CommentDto dto) {
CommentEntity entity = new CommentEntity();
updateEntity(entity, dto);
return entity;
}
@Override
public CommentDto createFrom(CommentEntity entity) {
CommentDto dto = new CommentDto();
if (entity != null) {
dto.setAuthor(entity.getAuthor());
dto.setCommentId(entity.getCommentId());
dto.setCommentData(entity.getCommentData());
dto.setCommentDate(entity.getCommentDate());
dto.setNew(entity.getNew());
}
return dto;
}
@Override
public CommentEntity updateEntity(CommentEntity entity, CommentDto dto) {
if (entity != null && dto != null) {
entity.setCommentData(dto.getCommentData());
entity.setAuthor(dto.getAuthor());
}
return entity;
}
}