[Java] Record로 DTO를 심플하게

🔥Log·2024년 7월 26일
0

Java

목록 보기
21/22

💡 이 글에서 사용된 코드: 깃헙

☕ 개요


Java로 개발을 하다보면, 하나의 도메인에 대해서도 핵심이 되는 객체 이외에도 수 많은 객체들이 사용되게 된다. 이러한 객체들은 대단한 기능을 갖고 있기 보다는 그냥 값을 들고 있거나 전달하는 역할을 한다. (Ex. DTO, DAO, VO 등등) 그래서 이런 객체들은 비슷한 패턴들을 보이고, 기계적으로 객체들을 만들고 다루게 된다.

여기서 하나의 문제가 발생하는데, Record가 나오기 이전의 Java에서는 이런 기계적이고 단순한 객체 하나 조차도 수 많은 보일러 플레이트 코드가 동반한다는 것이다. 그래서 사실은 굉장히 단순한 객체인데, 왠지 복잡해 보인다거나 그 핵심이 잘 파악이 안되는 경우가 많다.

이러한 문제를 해결해줄 녀석이 Record이다. 그저 값을 들고 있기만 하면 되는 성격의 객체들에 대해서 매우 심플하게 선언할 수 있게 해주는 문법이다.

그렇다면, Record에 대해서 한번 알아보자.



✍️ Record


위에서 얘기한 "그저 값을 들고 있기만 하면 되는 객체"는 다른 말로 "불변 객체(Immutable object)"라고 부를 수 있다. 값이 한번 할당되면, 바뀔 일이 없기 때문에 이러한 이름이 붙여진 것이다.

Record는 이 불변 객체를 표현하기 위해서 만들어진 문법이고, 아래와 같이 사용될 수 있다.

public record SomeDTO(
        Long id,
        String name
) {

}

객체이지만, 모양새는 클래스보다는 함수에 가까운데, 이 Record는 아래와 같은 특징들을 갖고 있다.

  • 변수들은 기본적으로 final, private 변수로 만들어진다.
  • Getter들이 자동으로 만들어진다.
  • 모든 매개변수를 사용하는 생성자가 자동으로 만들어진다.
  • 클래스명과 모든 값을 포함하는 toString()이 자동으로 만들어진다.
  • 모든 값이 동일할 때, true를 응답하는 equals()가 자동으로 만들어진다.
  • 모든 값을 사용하여 모든 값이 동일할 때 동일한 해시를 응답하는 hashCode()를 자동으로 만들어준다.

이런 특징들은 Record가 불변 객체를 표현하기에 포커스가 맞춰져있다는 것을 대변하기도 한고, 불변 객체라는 근본에 집중하기 위해서 부수적인 것들은 자동적으로 생성되도록 과감히 생략하였다.



🧐 비교


Record가 와 닿으려면 Record를 사용하지 않고 불변객체를 표현하려면 어떻게 해야하는지 비교해보면 된다.

1) Java 클래스

import java.util.Objects;

public class PureDTO {

    private final String title;
    private final String content;

    public PureDTO(String title, String content) {
        this.title = title;
        this.content = content;
    }

    public String getTitle() {
        return title;
    }

    public String getContent() {
        return content;
    }

    @Override
    public String toString() {
        return "PostCreateDTO[" +
                "title='" + title + '\'' +
                ", content='" + content + '\'' +
                ']';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        PureDTO that = (PureDTO) o;
        return Objects.equals(title, that.title) && Objects.equals(content, that.content);
    }

    @Override
    public int hashCode() {
        return Objects.hash(title, content);
    }

}

이 객체가 할 일은 titlecontent를 들고 있으면 되는 것인데, Getter, 생성자, toString, equals, hashCode와 같은 것들이 선언되어야한다. 사실 toString, equals, hashCode는 기본으로 생성되는 것들을 사용한다고 해도 Getter와 생성자 만으로도 뭔가 불.편.한 클래스가 된다.

2) Lombok

import lombok.*;

@Getter
@Builder
@ToString
@EqualsAndHashCode
@AllArgsConstructor
public class LombokDTO {

    private final String title;
    private final String content;

}

Lombok으로 표현하면 비교적 깔끔하긴 하지만, 여전히 어노테이션들이 덕지 덕지 붙어있고, privatefinal 키워드도 하나 하나 붙여줘야한다.

3) Record

public record RecordDTO(
        String title,
        String content
) {
	// 필요한 메서드나 Override하고 싶은 메서드 작성
}

위 클래스들과 동일한 역할을 하는 Record 객체이다.
굳이 설명할 것 없이 매우 심플한 모습인 것을 확인할 수 있다.



💡 Record와 Getter


Record는 문법 자체가 워낙 심플해서 특별히 설명할 것은 없는데, 하나만 짚고 넘어가자면 Record에서 Getter가 만들어지는 방식을 이야기할 수 있다.

String getTitle() {

}

관습적으로도 그렇고 Lombok도 그런데 보통 Getter는 getXXX()과 같은 컨벤션을 따른다.

public void main() {
	RecordDTO dto = new RecordDTO("title", "content");
    dto.getTitle(); // ❌
    dto.title(); // ✅
}

하지만 Record에서는 get이 빠지고 필드 이름과 동일한 메서드가 Getter로 생성된다. 이 점은 주의해서 사용하도록 하자.

개인적으로는 이런 방식 때문에 심플하게 json객체를 사용하는 것 같은 느낌이 들어서 좋은 것 같다 ㅋㅋ



🙏 참고


0개의 댓글