将Entity转换为DTO的最佳位置在哪里?

问题描述 投票:0回答:1

除了服务之外,还有什么更好的地方可以放置映射器来将实体转换为 DTO,反之亦然?

在工作中,我们总是把Mapper放到Service层,但我不确定这是最好的地方...

该服务包含一堆将 DTO 对象作为参数或返回 DTO 对象的方法。 当在控制器端调用这些方法时,这是很顺利的。 但有时我们在 ServiceA 中有方法调用 ServiceB 的方法,我想避免在不需要的地方转换对象:

示例:

// A SERVICE IMPL
public Integer save(ADto aDto) {
   AEntity aEntity = aMapper.toEntity(aDto);

   BEntity bEntity = aEntity.getBEntity();
   BDto bDto = bMapper.toDto(bEntity);
   bService.save(bDto);
   
   aEntity = aRepository.save(aEntity);
   return aEntity.getId();
}

// B SERVICE IMPL
public Integer save(BDto bDto) {
   BEntity bEntity = bMapper.toEntity(bDto);
   bEntity = bRepository.save(bEntity);
   return bEntity.getId();
}

在这种情况下,有 2 种转换是可以避免的:

  • bMapper.toDto(bEntity);
  • bMapper.toEntity(bDto);

所以我们最终得到了“重复”的方法:

Integer save(Entity entity)
Integer saveFromDto(Dto dto)
Integer save(List<Entity> entities)
Integer saveFromDtos(List<Dto> dtos)

Entity findById(Integer id)
Dto findByIdToDto(Integer id)
List<Entity> findAll()
List<Dto> findAllToDto()

我开始思考如何重构它,我想到了以下可能性:

  • 将Mapper移至Controller层 控制器是负责发送数据作为响应的层,因此该层必须知道必须发送什么,在我看来,知道要发送什么不是服务的责任。
    PRO:不需要为每个服务创建新的适配器类
    缺点:将转换逻辑添加到控制器中

  • 创建一个服务适配器并将其用于控制器层。
    基本上,适配器将负责:
    1 - 在调用需要实体作为参数的服务方法之前,调用映射器方法将 Dto 转换为实体。
    2 - 在将结果返回到Controller层之前调用mapper方法将实体转换为dto。
    PRO:不向控制器添加转换逻辑。
    缺点:每个服务都有一个新的适配器类。

此重构将使服务层免于额外的工作,因为它只了解实体类,而不了解 DTO。

java spring-boot oop design-patterns dto
1个回答
0
投票

对于像这样的设计问题,几乎没有任何不合格的通用答案,但一般来说,数据传输对象的存在是为了模拟应用程序如何与世界其他部分交互:JSON、XML、数据库行等。

在边界上,应用程序不是面向对象的,并且从任何面向对象的意义上来说,DTO 都不是真正合适的对象。正如 Martin Fowler 在 PEAA 中写道:

数据传输对象是我们母亲告诉我们永远不要写的对象之一。”

DTO 应该特定于外部 API,因此对于同一个域模型,您甚至可能有一个 DTO 用于将实体表示为 JSON,另一个 DTO 用于将其表示为数据库行。

在典型的端口和适配器架构中,DTO 和适配器是在应用程序的边界定义的。

如果您遵循依赖倒置原则,您希望核心领域模型不受“边缘”问题的影响,而“边缘”(或边界)可能依赖于并了解核心领域模型。

根据这一原则,所有涉及 DTO 的映射都必须与 DTO 本身存在于同一“层”。

人们经常发现清洁架构以一种有意义的方式解释了这一点。依赖关系可能只指向内部。

如果我正确理解 OP 的术语,请将映射器放在“控制器层”中,因为这也是 DTO 应该所在的位置,而 DTO 完全是“控制器层”关注的问题。

您不一定要将转换逻辑放入实际的控制器类中,但转换属于该层(例如库或模块)。

© www.soinside.com 2019 - 2024. All rights reserved.