[Java] DTO vs Record / Record를 DTO로 사용하는 이유?

nana·2025년 1월 1일
0

JAVA

목록 보기
10/11
post-thumbnail

내가 자바를 배우며 제일 신기했던 점은 데이터 전송하는 방식이 다양했다는 점이었다.
(다른 언어에도 있는데 굳이 신경쓰지 않았던 것일 수도..)
Java애플리케이션의 다양한 계층간, 서비스간에 데이터를 전송해야할 때 DTO를 사용하는데, Java14부터 새로운 기능인 Record가 도입되었다.
이 둘의 차이점과 사용법을 알아보고 어떤 것이 더 적합한지 알아보자.

DTO

DTO(Data Transfer Object)란

이름 그대로 데이터를 전송하는 객체이다.
복잡한 동작이나 로직 없이 데이터를 담기 위한 간단한 객체이며,
이 객체의 역할은 데이터를 묶어서 필요한 곳에 전달하는 것이다.

  • 애플리케이션의 계층 간에 데이터를 운반하는 컨테이너
  • 비즈니스 로직이나 복잡한 동작이 없고, 그저 데이터를 담고있는 역할만 한다.

DTO 구현요소

  • 데이터를 담는 private 필드
  • 데이터 접근, 수정을 위한 Getter/Setter메서드
  • 객체를 생성하는 생성자(Constructor)
  • 객체 비교, 출력시 유용한 toString(), hashCode(), equals()메서드 재정의(Override)

Lombok(롬복)같은 도구를 사용하면 반복적인 코드 작성 않고도 완전한 기능을 갖춘 DTO를 만들 수 있다.
하지만 Record는 기본적으로 불변성(immutability)를 가지며, 반복적인 코드를 제거하는 또 다른 방식을 제공한다.

구현 예제

import java.util.Objects;

public class UserDTO {
    private String username;
    private String password;

    //생성자
    public UserDTO(String username, String password) {
        this.username = username;
        this.password = password;
    }
    //getter, setter
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }

    //equals재정의
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserDTO userDTO = (UserDTO) o;
        return Objects.equals(username, userDTO.username);
    }

    //toString재정의
    @Override
    public String toString() {
        return "UserDTO{" +
                "name='"+ username +
                "password = " + password +'}';
    }
    
    //hashCode()재정의 
    @Override
    public int hashCode() {
        return Objects.hash(username);
    }
}

사용 방법

 public static void main(String[] args) {
        UserDTO userDTO = new UserDTO("nana", "password123");
        
        //데이터 접근 
        System.out.println(userDTO.getUsername());
        System.out.println(userDTO.getPassword());
        
        //toString() 
        System.out.println(userDTO);
        
        //비교 
        UserDTO userDTO2 = new UserDTO("nana", "password1234");
        System.out.println(userDTO.equals(userDTO2));
        
    }

커스터마이징(Customiztion)

DTO에서는 데이터 유효성 검사, 데이터 변환 메서드 또는 비즈니스 로직을 추가할 수 있다.
예를 들어, password필드가 유효한 형식인지 확인하는 검증 메서드를 추가할 수 있다.

Record는 커스터마이징이 제한적이다.
가볍고 불변성을 유지하도록 설계되었기 때문에 내부 상태를 수정하거나 복잡한 로직을 쉽게 추가할 수 없다.
데이터 객체에 커스터마이징된 동작이나 로직이 필요하다면 DTO가 더 유연한 선택이다.

Record

Record란

  • Java14에서 도입된 기능
  • 반복적으로 해야하는 많은 작업들을 자동으로 처리해준다.
    * Getter 자동 생성
    • 동등성 검사(equals)
    • toString()메서드 처리

주요 특징

  • 불변성 : Setter를 통해 데이터를 변경할 수 있는 DTO와는 다르게 한번 Record를 만들면 데이터를 변경할 수 없다.
  • 간결한 문법 : 필드만 선언하면 Java가 자동으로 생성자, Getter, equals(), hashCode(), toString()메서드를 만들어줌.
  • Setter없음 : 불변객체이므로 Setter메서드를 제공하지 않는다.

구현 예제

public record UserRecord(String userName, String password){

	public UserRecord{
    	validate(userName, password);
    }
}
  • 생성자 사용 시 파라미터 생략 가능, 초기화 로직은 마지막에 자동으로 호출(this.userName = userName)

사용 방법

public static void main(String[] args) {
        UserRecord userRecord = new UserRecord("nana", "password123");
        
        //데이터 접근 
        System.out.println(userRecord.userName());
        System.out.println(userRecord.password());
        
        //toString()사용 
        System.out.println(userRecord);
        
        //객체 비교 
        UserRecord userRecord1 = new UserRecord("nana", "password123");
        System.out.println(userRecord.equals(userRecord1));
    }

👉🏻 왜 Record를 사용할까?

불필요한 코드(Boilerplate Code) 감소

Boilerplate Code란?
컴퓨터 프로그래밍에서 보일러플레이트 또는 보일러플레이트 코드라고 부르는 것은 최소한의 변경으로 여러곳에서 재사용되며, 반복적으로 비슷한 형태를 띄는 코드를 말한다.

불변성 보장

객체 생성 이후 데이터 변경을 못하므로 다중 스레드 환경에서 더 안전하게 사용할 수 있다.

명확한 의도

Record를 사용하면 해당 객체가 추가적인 동작이나 로직 없이 데이터를 전달하기 위한 것임을 분명하게 알 수 있다.

데이터 표현방식이 깔끔

데이터를 간결하고 직관적으로 표현한다. Record 선언에는 필드만 포함되므로 코드가 더 깔끔하고 읽기 쉽다. 특히 데이터 모델이 많은 프로젝트에서 유지보수가 용이하다

함수형 프로그래밍과의 연관성

함수형 프로그래밍의 핵심 원칙 중 하나인 불변성(Immutability)과 잘 맞다.
불변 데이터 객체를 사용하고자 하는 시스템에 적합하다.

👉🏻 DTO vs Record, 언제 사용해야할까?

DTO 사용해야할 때

1. 데이터 수정이 필요한 경우

객체의 수명 주기 동안 데이터가 계속해서 업데이트 되는 상황
ex) 웹앱에서 사용자 등록 양식을 통해 일부 필드가 비어있는 상태로 UserDTO가 생성됨.
사용자가 프로필을 업데이트 하면서 UserDTO들의 값이 변경되어야 하는 경우.

2. 추가적인 동작이나 검증 로직이 필요한 경우.

데이터 객체가 단순히 데이터를 전달하는 것 이상으로 동작해야할 때, 예를들어 이메일 형식을 확인하거나 입력을 정제하는 로직이 필요할 떄.
=> DTO는 검증, 변환, 추가 메서드 등 맞춤형 동작을 추가하는 데 더 유연하다.

3. Java16 이전 버전과의 호환이 필요한 경우.

Java16 이전 버전의 프로젝트는 Record를 사용할 수 없다.
이런 경우에는 DTO나 Lombok과 같은 대안으로 코드를 간소화 해야한다.

Record를 사용해야 할 때

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

Record는 가볍고 불변성을 가진 객체로 데이터를 전달해야할 때 이상적이다.

마이크로 서비스 아키텍쳐(MSA)에서 서비스 간 데이터를 전달할 때 데이터를 수정할 필요가 없다면 Record사용이 유의미하다.

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

데이터를 전달하기만 하고 수정할 필요가 없을 경우.
불변성을 보장하여 데이터 일관성을 유지하므로, 데이터베이스에서 서비스 계층으로 또는 서비스간 데이터를 전달하는 데 적합하다.

3. 최신 Java(Java 16 이상)

최신Java 애플리케이션에서 데이터 표현을 간소화 하도록 설계되었기 때문에 기존 DTO가 가지고 있던 불필요한 반복적인 코드를 줄이는 데 도움이 된다.

Record의 한계

확장이 어렵다.
extends를 사용하여 다른 클래스를 상속할 수 없고, 필드가 final로 선언되기 때문.
주로 데이터를 전달하려는 목적으로 설계되었기 때문에 비즈니스 로직을 포함하기에 적절하지 않다.
Java14, 16 이전 버전에서는 호환이 불가능하다.

+ Record vs VO

Record와 VO 모두 객체의 상태가 변경되지 않는 것을 보장한다.
또 데이터를 캡슐화하여 표현하는 데 초점을 맞춘다.
VO는 값 기반의 동등성을 가지며, Record도 동일한 필드 값을 가지면 동일한 객체로 간주된다는 점이 공톰점이다.

VO는 도메인 모델내에서 특정 개념을 표현하고, 도메인 로직과 밀접하게 관련이 있다.
VO는 비즈니스 로직이나 규칙을 가질 수 있으나 Record는 단순히 데이터를 캡슐화하여 전달하는데 의미가 있다.

=> Record는 VO를 구현하는 데 적합하지만, VO의 모든 특성을 완벽히 대체하지는 않는다.
VO는 더 넓은 도메인 맥락에서 사용되고, 비즈니스 로직을 포함할 수 있다.

성능 고려사항

메모리 효율성

Record가 설계상 간결하므로 DTO보다 메모리를 조금 덜 사용할 수 있다.

불변성과 스레드 안정성

Record는 불변이기 때문에 스레드 간에 공유될 때 동기화나 잠금(locking)메커니즈이 필요하지 않다.
스레드 간 경쟁(thread contention)으로 성능이 저하되는 상황에서 성능을 향상할 수 있다.

반면, 가변(mutable) DTO를 멀티 스레드 환경에서 사용할 경우, 스레드 안정성을 보장하기 위해 접근을 동기화하거나 다른 메커니즘을 사용해야 하므로 성능에 추가적인 부담이 생기고 애플리케이션이 느려질 수 있다.

가비지 컬렉션

DTO와 Record 모두 일반적인 Java객체(POJO, Plain old Java Object)이므로 동일한 가비지 컬렉션 처리에 따라 관리된다.
하지만 Record가 더 간결하니 메모리에 적은 객체가 생성, 유지 될 수 있어 가비지 컬렉션이 조금 더 빠르게 이루어질 수 있다.
=> 대량의 데이터 객체를 처리하는 장기 실행 애플리케이션에서 성능 향상에 기여할 수 있다.

CPU 오버헤드

Record는 컴파일러에 의해 자동 생성 되며, 성능을 최적화하도록 설계되어 있어, 객체 생성, 메서드 호출, 비교작업에서 CPU성능이 조금 더 향상될 수 있다.
복잡한 DTO는 수동으로 구현된 메서드에서 비효율성이 발생할 수 있으나, Record는 일관되고 최적화된 방식으로 작업을 처리하므로 더 효율적이다.

실제 성능

DTO와 Record간의 성능 차이는 대부분 애플리케이션에서 매우 적거나 무시할만한 수준이다.
대용량 데이터 처리, 높은 처리량을 요구하는 애플리케이션, 또는 리소스가 제한된 환경에서만 눈에 띌 정도의 성능 차이를 경험할 수 있다.

추가 내용

왜 레코드는 getXXX가 아닐까?

  • 레코드를 선언할 때 간결성을 제공하지만 보일러 플레이트와의 전쟁을 선언하는 것은 목표가 아님.
  • Java Bean 명명 규칙을 사용하는 일반클래스의 문제를 해결하는 것은 목표가 아님.

Lombok을 사용하면 안될까?

  • 가독성 측면에서도 좋음
  • 롬복은 별도의 라이브러리나 외부 플러그인을 설치해 줘야한다.

Record를 도메인 객체에 사용할 수 있을까?

레코드는 불변 데이터를 전달하기 위한 캐리어이기 때문에 단순한 값의 집합을 표현하는 객체 지향적인 구성을 고안한다.
개발자가 확장 가능한 동작보다는 불변의 데이터 모델링에 집중할 수 있도록 도와준다.

💁🏻 마치며..

과제를 하면서 레코드 사용한 부분에 대해 좋은 피드백을 받았던 적이 있는데 사실 왜 잘 사용한건지 몰랐다.
근데 해당 내용을 공부하면서 왜 DTO가 아닌 Record사용으로 좋은 피드백을 받았던 것인지 이해가 되었다 :)


DTO vs Record in Java: Which Should You Use?
자바 DTO vs Record, 무엇을 사용해야 할까?
[10분 테코톡] 타칸의 Record

profile
BackEnd Developer, 기록의 힘을 믿습니다.

0개의 댓글