主要问题是如何在不违反SOLID原则的情况下将 DTO转换为实体和实体转换为Dtos 。
例如,我们有这样的json:
{ id: 1,
name: "user",
role: "manager"
}
DTO是:
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 pattern。
但在他们的例子中,没有OneToMany关系。为了创建UserEntity,我需要使用dao层(服务层)通过roleName获取角色。我可以将UserRepository(或UserService)注入到对话器中。因为转换器组件似乎会中断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;
}
....
在召集人类中使用存储库是否合适?或者我应该创建另一个负责从DTO创建实体的服务/组件(如UserFactory)?
答案 0 :(得分:2)
如果您有服务层,使用它进行转换或将任务委派给转换器会更有意义。
理想情况下,转换器应该只是转换器:映射器对象,而不是服务
现在,如果逻辑不是太复杂并且转换器不可重复使用,您可以将服务处理与映射处理混合使用,在这种情况下,您可以将Converter
前缀替换为Service
。
如果只有服务与存储库通信,它看起来也会更好 否则层变得模糊,设计混乱:我们不知道谁再调用谁。
我会这样做:
controller -> service -> converter
-> repository
或自行执行转化的服务(转换不是太复杂而且不可重复使用):
controller -> service -> repository
说实话,我讨厌DTO,因为这些只是数据重复
我只介绍它们,因为客户要求的信息与实体表示不同,并且使得自定义类(在这种情况下不是重复的)更加清晰。
答案 1 :(得分:1)
我建议您仅使用Mapstruct来解决您面临的这种实体到dto转换的问题。通过注释处理器,将自动生成从dto到实体以及从dto到实体(反之亦然)的映射,您只需像通常对存储库(@Autowired
)一样,将映射器中的引用注入到控制器中即可。
您还可以查看this示例,看看它是否符合您的需求。
答案 2 :(得分:1)
这就是我可能会这样做的方式。我将其概念化的方式是,用户转换器负责用户/用户dto转换,因此,它不应该对角色/角色dto转换负责。在您的情况下,角色存储库隐式充当用户转换器委派的角色转换器。如果我错了,也许对SOLID有更深入了解的人可以纠正我,但我个人认为这样可以解决问题。
尽管如此,我会犹豫的是,您将转换的概念与不一定要直观的数据库操作联系在一起,我想在进入数据库的几个月或几年中要特别小心。将来,某些开发人员不会在不了解性能注意事项的情况下无意中抓住该组件并使用它(无论如何,假设您是在较大的项目上进行开发)。我可能会考虑围绕包含缓存逻辑的角色存储库创建一些装饰器类。
答案 3 :(得分: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>>
转换单个对象时似乎有点麻烦,但是现在您可以批处理数据库请求,而不是一个一个地查询-用户不能使用所说的转换器错误(假定没有恶意)。
如果想要在定义转换器时更加舒适,请查看MapStruct或ModelMapper。最后但并非最不重要的一点是给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的方法,但是给定的上下文/场景正遭受不可提取的依赖关系的影响,这会影响性能,这使我认为强制SOLID可能不是一个好主意。这是一个权衡
答案 4 :(得分:1)
就个人而言,转换器应该位于控制器和服务之间,DTO唯一需要担心的是服务层中的数据以及如何向控制器公开哪些信息。
controllers <-> converters <-> services ...
在您的情况下,您可以使用JPA在持久层填充用户角色。
答案 5 :(得分:0)
您可以创建实体类本身的责任,而不是创建单独的转换器分类。
public class UserEntity {
// properties
public static UserEntity valueOf(UserDTO userDTO) {
UserEntity userEntity = new UserEntity();
// set values;
return userEntity;
}
public UserDTO toDto() {
UserDTO userDTO = new UserDTO();
// set values
return userDTO;
}
}
用法;
UserEntity userEntity = UserEntity.valueOf(userDTO);
UserDTO userDTO = userEntity.toDto();
通过这种方式,您可以将域名放在一个地方。您可以使用Spring BeanUtils设置属性。 您可以对RoleEntity进行相同的操作,并确定使用ORM工具加载UserEntity时是否要延迟/渴望加载。