DTO conveter pattern in Spring Boot

2022-09-04 21:04:36

主要问题是如何在不违反 SOLID 原则的情况下将 DTO 转换为实体,将实体转换为 Dtos
例如,我们有这样的json:

{ id: 1,
  name: "user", 
  role: "manager" 
} 

数字孪生是:

public class UserDto {
 private Long id;
 private String name;
 private String roleName;
}

实体是:

public class UserEntity {
  private Long id;
  private String name;
  private Role role
} 
public class RoleEntity {
  private Long id;
  private String roleName;
}

还有有用的Java 8 DTO conveter模式

但在他们的例子中,没有一对多的关系。为了创建用户实体,我需要使用 dao 层(服务层)按角色名称获取角色。我可以将 UserRepository(或 UserService)注入 conveter 吗?因为转换器组件似乎会破坏SRP,所以它只能转换,不能知道服务或存储库。

转换器示例:

@Component
public class UserConverter implements Converter<UserEntity, UserDto> {
   @Autowired
   private RoleRepository roleRepository;    

   @Override
   public UserEntity createFrom(final UserDto dto) {
       UserEntity userEntity = new UserEntity();
       Role role = roleRepository.findByRoleName(dto.getRoleName());
       userEntity.setName(dto.getName());
       userEntity.setRole(role);
       return userEntity;
   }

   ....

在 conveter 类中使用存储库好吗?或者我应该创建另一个服务/组件,负责从DTO创建实体(如UserFactory)?


答案 1

尝试将转换与其他层尽可能地分离:

public class UserConverter implements Converter<UserEntity, UserDto> {
   private final Function<String, RoleEntity> roleResolver;

   @Override
   public UserEntity createFrom(final UserDto dto) {
       UserEntity userEntity = new UserEntity();
       Role role = roleResolver.apply(dto.getRoleName());
       userEntity.setName(dto.getName());
       userEntity.setRole(role);
       return userEntity;
  }
}

@Configuration
class MyConverterConfiguration {
  @Bean
  public Converter<UserEntity, UserDto> userEntityConverter(
               @Autowired RoleRepository roleRepository
  ) {
    return new UserConverter(roleRepository::findByRoleName)
  }
}

您甚至可以定义一个自定义,但这可能会使整个抽象延伸得太远。Converter<RoleEntity, String>

正如其他人所指出的那样,这种抽象隐藏了应用程序的一部分,该部分在用于集合时可能表现非常差(因为数据库查询通常可以批处理)。我建议您定义一个在转换单个对象时可能看起来有点麻烦,但是您现在可以批量处理数据库请求而不是逐个查询 - 用户不能错误地使用所述转换器(假设没有恶意)。Converter<List<UserEntity>, List<UserDto>>

看看MapStructModelMapper,如果你想在定义转换器时有更多的安慰。最后但并非最不重要的一点是给datus一个机会(免责声明:我是作者),它允许您以流畅的方式定义映射,而无需任何隐式功能:

@Configuration
class MyConverterConfiguration {

  @Bean
  public Mapper<UserDto, UserEntity> userDtoCnoverter(@Autowired RoleRepository roleRepository) {
      Mapper<UserDto, UserEntity> mapper = Datus.forTypes(UserDto.class, UserEntity.class)
        .mutable(UserEntity::new)
        .from(UserDto::getName).into(UserEntity::setName)
        .from(UserDto::getRole).map(roleRepository::findByRoleName).into(UserEntity::setRole)
        .build();
      return mapper;
  }
}

(此示例在转换Collection<UserDto>

我认为这将是最可靠的方法,但是给定的上下文/场景正在遭受具有性能影响的不可分割的依赖关系,这使我认为强制SOLID在这里可能是一个坏主意。这是一种权衡


答案 2

如果您有一个服务层,则使用它来执行转换或使其将任务委托给转换器会更有意义。
理想情况下,转换器应该只是转换器:映射器对象,而不是服务。
现在,如果逻辑不太复杂并且转换器不可重用,则可以将服务处理与映射处理混合使用,在这种情况下,您可以将前缀替换为 。ConverterService

而且,如果只有服务与存储库通信,它似乎会更好。
否则,图层会变得模糊,设计会变得混乱:我们不再知道谁调用谁。

我会以这种方式做事:

controller -> service -> converter 
                      -> repository

或执行自身转换的服务(它的转换不是太复杂,并且不可重用):

controller -> service ->  repository            

现在说实话,我讨厌DTO,因为这些只是数据重复。
我之所以介绍它们,只是因为客户端在信息方面的要求与实体表示形式不同,并且拥有自定义类(在本例中不是重复类)确实更清楚。


推荐