인터넷을 검색하던 도중, 얼떨결에 DTO
를 쓰는 이유와 작성 방법에 대해 설명하는 글이 있어서 읽어보게 되었다.
거기서 DTO를 class
로 생성하지 말고, record
로 만들면 좋다는 내용이 있었다.
DTO가 무엇인지 다시 생각해보며, record와 잘 맞는 이유에 대해 생각을 정리해보는 시간을 가져보았다.
스프링 프로젝트를 해봤다면, DTO에 대해서 많이 들어봤을 것이다.
@Getter
public class LoginRequestDto{
private String username;
private String password;
}
DTO는 계층간에 데이터를 전달해주기 위해 사용되는 클래스이다. 그렇기에 DTO의 데이터가 쉽게 변하면 안된다는 특징을 갖고 있다.
그래서 보통 DTO 클래스의 필드는 final
속성을 갖거나 setter
를 선언하지 않는다. 게다가, DTO 클래스는 별도의 메소드를 포함하고있지 않으며, 단순히 데이터만을 포함하고 있다.
심지어 컨트롤러단에서 DTO를 생성할 때, Bean Validation을 통해서 값을 제한하거나 검증하기도 한다.
JDK 14 버전때 추가된 기능이다.
public record LoginRequestDto(
String username,
String password
) {}
record
는 class처럼 사용될 수 있지만, class와는 달리 몇 가지 특징을 가지고 있으며 탄생된 주된 이유는 불변성
이다.
record에 대한 몇 가지 특징에 대해 정리해보았다.
record는 선언된 모든 필드를 final
로 선언한다.
실제로, 필드 값을 변경하려고 시도하면 사진과 같이 컴파일 오류를 볼 수 있다.
기본적으로 getter
를 제공하며, getX()
처럼 주어지는것이 아니라 x()
처럼 네이밍 처리가 된다.
record Range(int lo, int hi) {}
public class Main{
public static void main(String[] args){
Range r = new Range(5, 10);
r.lo();
r.hi();
}
}
==
기호는 적용되지 않는다. public void test() {
LoginRequestDto loginRequestDto1 = new LoginRequestDto("1", "2");
LoginRequestDto loginRequestDto2 = new LoginRequestDto("1", "2");
System.out.println(loginRequestDto2);
// LoginRequestDto[email=1, password=2]
System.out.println(loginRequestDto1 == loginRequestDto2); // false
System.out.println(loginRequestDto1.hashCode() == loginRequestDto2.hashCode()); // true
System.out.println(loginRequestDto1.equals(loginRequestDto2)); // true
}
이를 이용해, record도 Bean Validation
을 적용시킬 수 있다.
public record LoginRequestDto(
@Email String email,
@NotEmpty String password
) {}
생성자에 조건식을 쉽게 작성할 수 있는 문법을 제공한다.
public record Person(String name, String address) {
public Person {
Objects.requireNonNull(name);
Objects.requireNonNull(address);
}
}
위의 예시와 같이, 소괄호를 없애고 중괄호에 검증 코드를 넣어서 예외를 발생시키도록 작성하면 된다.
record는 다른 클래스를 상속받지 못한다.
record에 Lombok을 적용시킬 순 있지만, @Builder
를 제외한 나머지 어노테이션은 대부분 record에서 이미 제공되기 때문에 사용할 일이 잘 없을 것 같다.
@Builder
public record LoginRequestDto(String email, String password) {}
public class Test {
public void test() {
LoginRequestDto dto = LoginRequestDto.builder()
.email("email")
.password(null)
.build();
}
}
단, @Setter
처럼 필드에 영향을 끼치는 어노테이션은 사용을 지양하는 것이 좋아보인다.
DTO는 계층간에 데이터를 전달하는 용도로 많이 쓰이며, 단순히 전달해줄 데이터값만 포함하고 있어야 한다. DTO에는 특정한 로직이 포함된 메소드를 갖지 않는 것이 좋으며, 데이터가 도중에 변하지 않도록 설정해줄 필요가 있다.
recodr는 일반 클래스처럼 사용할 수 있지만, 필드에 final
을 자동으로 추가해주는 특징을 가지고 있으며 getter와 equals/hashCode, toString 메소드를 기본적으로 지원해주므로 개발자에게 편리한 개발 환경을 제공해준다.
DTO를 record로 구현한다면, DTO가 가져야 할 특징을 쉽게 구현할 수 있다.
그러므로, 온전히 데이터 불변성을 지키는 DTO 클래스를 생성할 필요가 있다면, record를 고려해봐도 좋을 것 같다.
https://www.youtube.com/watch?v=0RyECSrkyOg&ab_channel=%ED%95%98%EC%B0%AE%EC%9D%80%EC%98%A4%ED%9B%84
https://www.baeldung.com/java-record-keyword