일반적으로 DTO의 경우 각 레이어사이에서만 사용되므로, 변하지 않는경우가 매우 많다.
이를 위해서 JAVA 16부터는 Record라는 클래스를 제공해서, getter(),hashcode(),equals(),toString()을 자동으로 완성해주고, 변수들이 final로 자동으로 선언되어서 불변성을 보장해준다.
// 기존 클래스 기반 DTO
public class MemberDto {
private final String name;
private final String email;
private final int age;
public MemberDto(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}
public String getName() {
return name;
}
public String getEamil() {
return email;
}
public int getAge() {
return age;
}
}
이렇게 긴 형태의 dto를 아래와 같이 짧은 형태로 바꿀 수 있다.
// Record. 생성자, getter, hashCode(), equals(), toString() 자동 완성
public record MemberDto(String name, String email, int age) {}
그렇다면 dto를 사용할 때 반드시 record를 사용해야할까?
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public UserResponseDto getUserWithMaskedEmail(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
// DTO 생성
UserResponseDto dto = new UserResponseDto(user);
// 이메일 마스킹 처리 (비즈니스 로직 적용)
dto.setEmail(maskEmail(dto.getEmail()));
return dto;
}
private String maskEmail(String email) {
if (email == null || !email.contains("@")) {
return "*****";
}
String[] parts = email.split("@");
return parts[0].substring(0, 2) + "****@" + parts[1];
}
}
해당 로직은 DTO를 변경해야 하는 사례이다. 기본적으로 dto를 사용하는 경우, 가변하는 경우도 있으므로 record 클래스를 사용하지 않거나, record를 사용하고, 새로운 dto를 만든다.
객체가 너무 많아진다면, record 키워드를 사용하지 않고 dto를 생성하는 것이 옳다고 생각한다.
VO는 Value Object로써, 즉 값을 객체화한 것이다.
기본자료형 자료형의 경우 변수에 대한 검증로직을 비즈니스 로직 사이에 넣어야할 수 밖에 없다. 이러한 변수의 검증 로직을 자료형과 같이 보관한다면 훨씬 비즈니스 로직과 자료형사이의 독립성을 보장할 수 있을 것이다.
이를 위해서 변수를 객체에 보관해서 사용하는데, 이를 VO라고한다.
DTO와 큰 차이가 없다고 보여지긴하나, 엄밀히 말하면 다르다.
DTO내에는 검증로직이 없고, 단순하게 변수의 저장용도로 사용되나. VO는 위에서말한 것처럼 검증로직을 할 수 있다는 것이 차이이다.
public class UserDTO {
private String name;
private int age;
public UserDTO(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
dto 예시
import java.util.Objects;
public class Age {
private final int value;
public Age(int value) {
if (value < 0) {
throw new IllegalArgumentException("나이는 0 이상이어야 합니다.");
}
this.value = value;
}
public int getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Age age = (Age) o;
return value == age.value;
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
검증 로직이나 변수 관련 비즈니스 로직을 작성한다면, 위처럼
VO로 객체를 생성하는게 옳은 방향성이라고 보여진다.