[JAVA] DTO를 record 타입으로 생성하면 좋은 이유

beegle·2025년 2월 16일
post-thumbnail

최근 DTO를 record 타입으로 사용하면 좋다고 어디선가 듣고 사용중인데 편해서 잘 쓰고 있다. 그래서 DTO를 record 타입으로 설정하면 좋은 이유에 짤막하게 소개해보려 한다.

📌 record란?

record 타입은 자바 14 버전에 처음 소개된 클래스 타입으로, 16버전에서 정식 스텍이 됐다. 자바 17을 쓴다면 사용할 수 있다. (8 버전이라면… 고멘)

기본적으로 필드값이 바뀌지 않는 불변 객체이며, 메서드나 생성자를 자동으로 만들어줘서 간결하고 효율적인 데이터 객체를 만들 수 있다.
즉, 계층간 데이터 교환을 위해 사용하는 객체인 DTO(Data Transfer Object)를 만드는데 특화되어있는 클래스 타입이다.

public record MemberRequest(
        String nickname,
        String password,
        String githubUrl,
        JobRole favoriteJob) {
}
record 레코드명(컴포넌트1, 컴포넌트2, ...) { }

record는 변수 선언을 위 예시와 같이 생성자 매개변수 나열하듯 써주면 된다. 이렇게 헤더에 나열되는 필드를 컴포넌트라고 부르며, 반대로 레코드 내부에는 멤버 변수를 선언할 수 없다.

주요 특징

  • 생성자, getter, equals(), hashCode(), toString() 메서드를 자동으로 제공한다.
  • Record 클래스의 접근제어자는 public, package-private 만 가능하다.
  • record는 불변 객체로 abstract로 선언할 수 없으며 암시적으로 final로 선언된다.
    한 번 값이 정해지면 setter를 통해 값을 변경할 수 없으며 상속을 할 수 없다. (implements는 가능하다)
  • record 내 각 필드(헤더에 나열한 컴포넌트)는 private final로 정의된다.
  • 레코드 내부에 멤버 변수(인스턴스 필드)를 선언할 수 없다. 그러나 static 변수는 생성이 가능하다. 이는 헤더에서 정의한 멤버만을 record에서 관리하기 위함이다.
  • 이를 제외하고는 일반 클래스 타입과 동일하게 사용 가능하다
    • new 키워드를 통해 객체화 가능
    • static 메소드, static 필드 선언 가능
    • 중첩 클래스 사용 가능 및 제너릭 타입으로 지정 가능

📌 record의 장점

@Builder
public record QuestionDto(
        Long id,
        String content,
        String standardAnswer,
        Long answerCount,
        Long challengeCount,
        Double answerAverage
) {
    public static QuestionDto from(Question question) {
        return QuestionDto.builder()
                .id(question.getId())
                .content(question.getContent())
                .standardAnswer(question.getStandardAnswer())
                .answerCount(question.getAnswerCount())
                .answerAverage(question.getAnswerAverage())
                .challengeCount(question.getChallengeCount())
                .build();
    }
}

1. 간결성

  • 가독성이 너무 좋다. 모든 장점 중 가장 큰 장점이라고 생각한다.
  • 실제 사용하는 record 코드를 들고 왔다. 저게 DTO 코드의 전부다. 너무나 간결하고 이해하기 쉽다. 당장 사용하자.
  • 생성자, getter, equals(), hashCode(), toString() 등 DTO 특성의 클래스를 개발할때 매번 개발자가 직접 구현해주어야 했던 메서드를 자동으로 만들어주기 때문에 불필요한 반복 작업을 줄여준다.
  • 보통 어노테이션을 많이 달아줄텐데 그마저도 생략해줄 수 있다. (저는 빌더 사랑꾼이라 @Builder만 추가로 달아줍니다 ㅎㅎ)
  • 모든 컴포넌트가 기본적으로 private final로 정의되기 때문에 생략 가능하다.
  • 위 예시 처럼 record 선언 후 정적 메서드 패턴 of, from, toEntity 세 가지를 주로 쓰는데 DTO를 정말 깔끔하게 작성할 수 있게 된다. (정적 메서드 패턴 이게 또 요물인데 DTO에서 생성자 대신 쓰기 정말 좋다.)

2. 불변성

  • 모든 컴포넌트가 final로 선언되며, setter가 금지되어 있기 때문에 데이터의 불변성을 보장한다.

3. 패턴 매칭 통합

  • java의 패턴 매칭과 함께 사용될 수 있어, 데이터 추출 및 패턴 일치 검사 등 모두 사용 가능하다.

4. “데이터 전달”이란 목적을 명시

  • "이 클래스는 데이터 전달 용도이다"를 명시적으로 나타낼 수 있으므로 코드에 대한 이해를 돕고 해당 클래스의 목적에 맞지 않는 구현을 하지 않도록 방지합니다.

🔍결론

아래 세 가지 경우라면 DTO를 생성할 때 record로 생성하는 것을 추천한다!!

1. 간결하고 불변성을 가진 데이터 전달 객체가 필요한 경우

2. 읽기 전용 데이터 전송이 필요한 경우

3. 최신 Java 애플리케이션(Java 16 이상)에서

profile
배운 내용을 이해하기 쉽게 다시 정리하는 공간입니다.

0개의 댓글