레이어 간 의존성 관리를 위해 ui와 service, repository마다 모두 다른 dto를 사용한다고 가정해보자.
이 경우 dto와 dto간 변환할 때마다 귀찮은 과정을 거쳐야 한다.
User를 예시로 들어보자.
repository 레이어의 User Entity와 service 레이어의 UserDto를 서로 변환하는 방법은 다음과 같다.
1. Dto 내에서 객체 변환 메소드 만들기
User entity
@Entity
@Builder
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(Strategy = GenetrationType.IDENTITY)
private Long id;
private String userId;
private String email;
private String encryptedPwd;
}
UserDto
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
private String email;
private String userId;
priavet String pwd;
private String encryptedPwd;
public User toEntity() {
User.build()
.email(email)
.userId(userId)
.encryptedPwd(encryptedPwd)
.build();
}
public static UserDto from(User user) {
this.email = user.getEmail();
this.userId = this.getUserId();
this.name = this.getName();
}
}
서비스에서 사용하는 dto는 RequestDto와 ResponseDto를 나누어 사용할 수도 있지만, 편의상 합쳐서 UserDto로 사용하겠다.
toEntity 메서드를 통해 UserDto -> User 엔티티로 변환한다.
from 메서드를 통해 User 엔티티 -> UserDto로 변환한다.
Service에서 사용
@Service
@RequiredArgsConstructor
public class UserServiceImpl {
private final UserRepository userRepository;
public UserDto createUser(UserDto userDto) {
...
// UserDto -> UserEntity 변환
User user = userDto.toEntity();
userRepository.save(user);
// UserEntity -> UserDto 변환
UserDto responseUserDto = UserDto.from(user);
return responseUserDto;
}
}
간단한 코드로 변경이 가능하다.
2. Service 에서 직접 변환하기
@Service
@RequiredArgsConstructor
public class UserServiceImpl {
private final UserRepository userRepository;
public UserDto createUser(UserDto userDto) {
...
// UserDto -> UserEntity 변환
User user = User.build()
.email(userDto.getEmail())
...
.build();
userRepository.save(user);
// UserEntity -> UserDto 변환
UserDto responseUserDto = new UserDto(
user.getEmail();
...
);
return responseUserDto;
}
}
코드가 길어져 가독성이 떨어질 수 있고, 서비스의 메소드마다 일일이 만들어줘야한다는 단점이 있다.
3. ModelMapper 라이브러리 사용하기
dto에 메소드를 만들어서 객체간의 변환을 하면, Service나 UI 레이어에서 사용은 편리해질 수 있지만 모델이나 요구사항의 변경 등이 있을때 일일이 코드를 바꿔줘야 하는 단점이 있다.
ModelMapper 라이브러리는 클래스 자체를 일괄로 변경해주기 때문에, 이러한 단점을 해결할 수 있다.
사용법은 objectMapper와 비슷하다.
1) ModelMapper 의존성 추가 (maven 기준)
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.8</version>
</dependency>
사용하는 프레임워크에 맞는 버전을 선택하면 된다.
2) Service에서 사용
@Service
@RequiredArgsConstructor
public class UserServiceImpl {
private final UserRepository userRepository;
public UserDto createUser(UserDto userDto) {
...
// ModelMapper 설정하기
ModelMapper mapper = new ModelMapper();
mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
// UserDto -> UserEntity 변환
User user = mapper.map(userDto, User.class);
// UserEntity -> UserDto 변환
UserDto responseUserDto = mapper.map(user, UserDto.class);
return responseUserDto;
}
}
MatchingStrategies.STRICT는 java 객체의 이름이 동일해야 클래스 변환을 해준다는 설정이다.
간단한 ModelMapper 설정을 통해 쉽게 객체간 변환해줄 수 있다.
여기서 주의할 점은, ModelMapper는 기본적으로 Setter를 사용해서 매핑하기 때문에 Setter사용을 원하지 않는다면 따로 설정이 필요하다.
나의 경우 일일이 new로 만들어주기보다는 설정값까지 한번에 @Bean으로 등록해서 필요한 곳에서 주입받아 사용하고 있다.
다음과 같은 설정이 추가적으로 필요하다.
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT)
.setFieldAccessLevel(Configuration.AccessLevel.PRIVATE)
.setFieldMatchingEnabled(true);
return modelMapper;
}
ModelMapper도 장점만 있는 것은 아니다.
Dto 내에서 직접 코드를 추가해서 사용하면 커스텀이 용이하지만, ModelMapper는 그렇지 않다는 단점이 있을 수 있다.
상황에 따라서 더 적절한 것을 사용하면 된다.
'백엔드 Backend > 기본 개념' 카테고리의 다른 글
RestAPI - Dto에서 @JsonInclude 사용하기 (2) | 2025.01.08 |
---|---|
Cloud Native와 MSA (0) | 2025.01.03 |
메시지 큐 (0) | 2024.08.20 |
실시간 통신 기능 (1) (1) | 2024.08.14 |
캐시 / 캐시 전략 (1) | 2024.08.12 |