회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
AWS 이용 중이라면 최대 700만 원 지원받으세요
본문은 요즘IT와 번역가 Jane Heo가 함께 아슈토쉬 크리슈나(Ashutosh Krishna)의 글 <DTO vs Record in Java: Which Should You Use?>을 번역한 글입니다. 필자인 아슈토쉬 크리슈나는 쏘트웍스(Thoughtworks) 인도지사에서 애플리케이션 개발자로 일하며, 해쉬노드(Hashnode), 미디엄(Medium) 등에서 개발자들을 위한 글을 연재 중입니다.
회원가입을 하면 원하는 문장을
저장할 수 있어요!
다음
회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!
확인
본문은 요즘IT와 번역가 Jane Heo가 함께 아슈토쉬 크리슈나(Ashutosh Krishna)의 글 <DTO vs Record in Java: Which Should You Use?>을 번역한 글입니다. 필자인 아슈토쉬 크리슈나는 쏘트웍스(Thoughtworks) 인도지사에서 애플리케이션 개발자로 일하며, 해쉬노드(Hashnode), 미디엄(Medium) 등에서 개발자들을 위한 글을 연재 중입니다.
필자에게 허락을 받고 번역했으며, 글에 포함된 각주(*표시)는 ‘번역자주’입니다.
오늘은 Java 개발에서 DTO(Data Transfer Object, 데이터 전송 객체)와 Record(레코드)의 차이점과 사용 사례를 살펴보려고 합니다. 우리는 종종 Java 애플리케이션의 다양한 계층 간 또는 서비스 간에 데이터를 전송해야 합니다. 이를 위해 DTO(Data Transfer Object, 데이터 전송 객체)를 사용합니다. DTO는 복잡한 동작이나 로직 없이 데이터를 담기 위한 간단한 객체입니다. 이 객체의 역할은 데이터를 묶어서 필요한 곳에 전달하는 것이죠.
그런데 Java 14에서 새로운 기능인 ‘Record’가 도입되었습니다. Record는 DTO와 마찬가지로 데이터를 담는 데 초점을 맞춘 특별한 클래스 유형입니다. 중요한 차이점은 Record는 우리가 반복적으로 해야 하는 많은 작업을 자동으로 처리해 준다는 것입니다. 예를 들어, *Getter(게터)를 자동으로 생성하고, 동등성 검사나 toString() 메서드도 알아서 처리해 줍니다. 이 기능은 Java 16에서 완전히 사용 가능해지면서 Record는 Java에서 데이터를 처리하는 깔끔하고 현대적인 방법이 되었습니다.
*데이터를 가져오는 메서드
그렇다면 왜 DTO와 Record를 비교하는 걸까요? 그 이유는 둘 다 데이터를 운반하는 비슷한 목적을 가지고 있기 때문입니다. 하지만 Java가 계속 발전함에 따라 언제 DTO를 사용하고 언제 Record를 사용해야 하는지 이해하는 것이 중요합니다. 이 글에서는 DTO와 Record의 차이점을 알아보고, 특히 현대적인 Java 애플리케이션에서 어떤 것이 더 적합한지 결정하는 데 도움을 드리고자 합니다.
DTO(Data Transfer Object)는 애플리케이션의 다양한 부분 간에 데이터를 이동시키는 데 사용되는 간단한 Java 객체입니다. 애플리케이션의 계층 간에 데이터를 운반하는 컨테이너라고 생각할 수 있죠. 예를 들어, 웹 애플리케이션에서 DTO는 서비스 계층에서 컨트롤러로 데이터를 전송하거나, 컨트롤러에서 뷰 계층으로 데이터를 전달할 때 사용될 수 있습니다.
DTO는 애플리케이션의 다른 부분들을 서로 분리하는 데 도움을 주어, 코드가 더 체계적이고 유지 관리하기 쉬워집니다. 일반적으로 DTO에는 비즈니스 로직이나 복잡한 동작이 없습니다. 그저 데이터를 담고 있는 역할만 합니다.
DTO는 보통 일반적인 Java 클래스로 구현됩니다. 일반적인 DTO는 다음과 같은 요소를 포함합니다.
다음은 UserDTO 클래스의 예입니다:
import java.util.Objects;
public class UserDTO {
private String name;
private int age;
private String email;
// 생성자
public UserDTO(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Getter와 Setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// toString() 메서드 재정의
@Override
public String toString() {
return "UserDTO{" +
"name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
'}';
}
// 객체 비교를 위해 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 age == userDTO.age && Objects.equals(name, userDTO.name) && Objects.equals(email, userDTO.email);
}
// hashCode() 메서드 재정의
@Override
public int hashCode() {
return Objects.hash(name, age, email);
}
이 UserDTO 클래스는 사용자에 대한 정보를 담고 있습니다: 이름, 나이, 이메일과 같은 정보입니다. 또한 두 개의 UserDTO 객체를 비교할 수 있는 equals() 메서드, 고유한 해시 코드를 생성하는 hashCode() 메서드, 그리고 읽기 쉽도록 출력하는 toString() 메서드와 같은 기본 기능도 제공합니다.
*Lombok(롬복) 같은 도구를 사용하면 반복적인 코드를 직접 작성하지 않고도 완전한 기능을 갖춘 DTO를 만들 수 있습니다. 하지만 Record는 기본적으로 불변성(immutability)을 가지며, 반복적인 코드를 제거하는 또 다른 방식을 제공합니다.
*Java 코드를 더 간단하게 만들어주는 자동화 도구
UserDTO를 사용하는 방법은 다음과 같습니다:
public class UserDTOUsageExample {
public static void main(String[] args) {
UserDTO user = new UserDTO("Ashutosh", 25, "ashutosh@example.com");
// 데이터 접근
System.out.println(user.getName());
System.out.println(user.getAge());
System.out.println(user.getEmail());
// toString() 메서드 사용
System.out.println(user);
// 객체 비교
UserDTO anotherUser = new UserDTO("Vishakha", 22, "vishakha@example.com");
System.out.println(user.equals(anotherUser));
}
}
출력:
Ashutosh
25
ashutosh@example.com
UserDTO{name='Ashutosh', age=25, email='ashutosh@example.com'}
false
Record는 Java 14에서 미리보기 기능으로 도입되었고, Java 16에서 정식으로 출시된 특별한 유형의 클래스입니다. Record는 불변(Immutable) 데이터를 간결하고 읽기 쉽게 담는 데 초점을 맞추고 있으며, 기존 클래스(DTO 등)에서 필요했던 반복적인 코드를 많이 줄여줍니다.
DTO에서는 생성자, Getter, equals(), hashCode(), toString() 메서드를 직접 작성해야 합니다. 하지만 Record를 사용하면 Java가 이 모든 것들을 자동으로 생성해 줍니다. 덕분에 간단한 불변 객체, 즉 데이터만 담는 역할을 하는 객체를 만들 때 매우 유용합니다.
다음은 앞서 보았던 UserDTO와 동일한 사용자 데이터를 담는 UserRecord를 Record로 만드는 예입니다:
public record UserRecord(String name, int age, String email) {
}
이게 끝입니다. 단 한 줄만으로 Java는 자동으로 다음을 생성해 줍니다:
UserRecord를 사용하는 방법은 다음과 같습니다:
public class UserRecordUsageExample {
public static void main(String[] args) {
UserRecord user = new UserRecord("Ashutosh", 25, "ashutosh@example.com");
// 데이터 접근
System.out.println(user.name());
System.out.println(user.age());
System.out.println(user.email());
// toString() 메서드 사용
System.out.println(user);
// 객체 비교
UserRecord anotherUser = new UserRecord("Vishakha", 22, "vishakha@example.com");
System.out.println(user.equals(anotherUser));
}
}
출력:
Ashutosh
25
ashutosh@example.com
UserRecord[name=Ashutosh, age=25, email=ashutosh@example.com]
false
UserRecord를 사용하면 Getter, 생성자, equals(), hashCode(), toString() 메서드를 일일이 작성할 필요가 없습니다. Record는 불변 객체를 간단하고 깔끔하게 만들 방법을 제공합니다.
이제 DTO와 Record에 대해 알아봤으니, 어떤 차이가 있는지 비교해 보겠습니다.
Record는 기본적으로 불변성을 가지며, 한 번 생성된 Record 인스턴스의 데이터는 변경할 수 없습니다. 이러한 불변성 덕분에 데이터는 일관성을 유지하며, 추가적인 코드 없이도 스레드 안전(Thread-safe)합니다. 예를 들어, UserRecord에서는 name, age, email 필드가 생성될 때만 설정되고, 이후에는 수정할 수 없습니다.
반면, DTO는 일반적으로 가변성을 가지며, 객체가 생성된 후에도 필드를 변경할 수 있습니다. DTO를 불변으로 만들려면 Setter 메서드를 사용하지 않거나, final 필드를 사용하여 신중하게 설계해야 합니다. 불변성을 구현하는 방식은 다음과 같습니다.
public class ImmutabilityExample {
public static void main(String[] args) {
UserDTO userDTO = new UserDTO("Ashutosh", 25, "ashutosh@example.com");
userDTO.setAge(26); // DTO는 기본적으로 값 변경을 허용합니다.
UserRecord userRecord = new UserRecord("Ashutosh", 25, "ashutosh@example.com");
userRecord.name = "Jane"; // 이는 컴파일 에러가 발생합니다.
}
}
Record의 큰 장점 중 하나는 보일러플레이트 코드, 즉 반복적인 코드를 크게 줄일 수 있다는 점입니다. DTO를 사용할 때는 보통 Getter, Setter, 생성자, equals(), hashCode(), toString() 메서드를 직접 작성해야 하지만, Record는 이러한 메서드들을 자동으로 생성해 줍니다.
반대로, DTO는 직접 코드를 작성해야 합니다. Lombok 같은 도구를 사용하면 보일러플레이트 코드를 줄일 수 있지만, Record만큼 간단하지는 않습니다.
Record는 데이터를 간결하고 직관적으로 표현하는 방법을 제공합니다. Record 선언에는 필드만 포함되므로 코드가 더 깔끔하고 읽기 쉽습니다. 특히 데이터 모델이 많은 프로젝트에서 유지보수가 더 용이합니다.
예시:
// Record: 간결하고 단순한 코드
public record UserRecord(String name, int age, String email) {}
반면, DTO는 코드가 더 길어집니다:
// DTO: 더 많은 코드 필요
public class UserDTO {
private String name;
private int age;
private String email;
// 생성자, Getter, Setter, toString(), equals(), hashCode()...
}
Record는 단순히 데이터를 전달하는 용도임을 명확하게 드러내며, DTO는 반복적인 코드나 추가 로직으로 인해 복잡해질 수 있습니다.
DTO의 장점 중 하나는 커스터마이징이 용이하다는 점입니다. DTO에서는 데이터 유효성 검사, 데이터 변환 메서드 또는 비즈니스 로직을 추가할 수 있습니다(순수 DTO에서는 권장되지 않지만 가능). 예를 들어, email 필드가 유효한 형식인지 확인하는 검증 메서드를 추가할 수 있습니다.
반면, Record는 커스터마이징이 제한적입니다. Record는 가볍고 불변성을 유지하도록 설계되었기 때문에 내부 상태를 수정하거나 복잡한 로직을 쉽게 추가할 수 없습니다. 만약 데이터 객체에 커스터마이징된 동작이나 로직이 필요하다면, DTO가 더 유연한 선택입니다.
DTO에서 커스터마이징 예시:
public class UserDTO {
private String email;
// 이메일 형식을 검증하는 메서드
public boolean isValidEmail() {
return email != null && email.contains("@");
}
}
Record에서는 이런 커스터마이징은 보통 Record 외부에서 처리해야 합니다. Record는 데이터를 전달하는 데만 집중하고, 검증 같은 로직은 다른 곳에서 처리하는 것이 일반적입니다.
함수형 프로그래밍의 핵심 원칙 중 하나는 불변성(Immutability)입니다. 즉, 데이터 객체가 한 번 생성된 후에는 변경되지 않아야 한다는 것입니다. Record는 기본적으로 불변이므로 함수형 프로그래밍 원칙과 잘 맞습니다. 따라서 불변 데이터 객체를 사용하고자 하는 시스템에 적합한 선택입니다.
Record:
반면, DTO는 가변성을 가지며, 불변으로 만들려면 수동적인 설정이 필요합니다. DTO는 상태 변경이 흔한 객체 지향 프로그래밍 스타일에 더 적합합니다.
DTO:
DTO와 Record 중 어느 것을 사용할지 결정할 때는 사용 사례, 프로젝트 요구사항, 그리고 사용하는 Java 버전에 크게 좌우됩니다.
1. 데이터 수정이 필요한 경우
객체의 데이터를 생성 후 수정해야 할 때는 DTO가 더 적합합니다. DTO는 보통 가변적이어서 필드 값을 필요에 따라 변경할 수 있습니다. 객체의 수명 주기 동안 데이터가 계속해서 업데이트되는 상황에서 유용합니다.
예시) 웹 애플리케이션에서, 사용자 등록 양식을 통해 처음에 일부 필드가 비어 있는 상태로 UserDTO가 생성될 수 있습니다. 사용자가 프로필을 업데이트하면서 해당 UserDTO의 값들이 변경되어야 할 수도 있습니다.
2. 추가적인 동작이나 검증 로직이 필요한 경우
DTO는 검증, 변환, 또는 추가 메서드 등 맞춤형 동작을 추가하는 데 더 유연합니다. 데이터 객체가 단순히 데이터를 전달하는 것 이상으로 동작해야 할 때 예를 들어, 이메일 형식을 확인하거나 입력을 정제하는 로직이 필요할 때는 DTO가 더 적합합니다.
예시) UserDTO에 이메일 형식을 검증하는 메서드를 추가하여, 애플리케이션 계층 간에 데이터를 전달하기 전에 이메일이 유효한지 확인할 수 있습니다.
3. 이전 버전의 Java(16 이전 버전)와 호환이 필요한 경우
Java 16 이전 버전을 사용하는 프로젝트라면 Record를 사용할 수 없습니다. 이런 경우에는 기존의 DTO나 Lombok(롬복)과 같은 대안으로 코드를 간소화해야 합니다.
예시) 만약 애플리케이션이 Java 11 또는 Java 8을 지원해야 한다면 Record는 선택지가 아니므로, 기존의 DTO 방식을 유지해야 합니다.
1. 간결하고 불변성을 가진 데이터 전달 객체가 필요한 경우
Record는 가볍고 불변성을 가진 객체로 데이터를 전달해야 할 때 이상적입니다. Record는 필수적인 메서드들(생성자, Getter, equals(), hashCode(), toString())을 자동으로 생성해 주기 때문에 데이터 표현을 간결하고 효율적으로 처리할 수 있습니다.
예시) 마이크로서비스 아키텍처에서 서비스 간 데이터를 전달할 때 데이터를 수정할 필요가 없다면, UserRecord가 완벽한 선택일 수 있습니다.
2. 읽기 전용 데이터 전송이 필요한 경우
애플리케이션에서 데이터를 전달하기만 하고 수정할 필요가 없다면 Record를 사용하는 것이 좋습니다. Record는 불변성을 보장하여 데이터 일관성을 유지하기 때문에, 데이터베이스에서 서비스 계층으로 또는 서비스 간 데이터를 전달하는 데 적합합니다.
예시) Record는 데이터베이스 계층에서 REST 컨트롤러로 사용자 데이터를 전달할 때 사용할 수 있습니다.
3. 최신 Java 애플리케이션(Java 16 이상)에서
프로젝트에서 Java 16 이상을 사용하고 있다면 Record를 충분히 활용할 수 있습니다. Record는 최신 Java 애플리케이션에서 데이터 표현을 간소화하도록 설계되었으며, 기존 DTO가 가지고 있던 불필요한 반복적인 코드를 줄이는 데 도움이 됩니다.
예시) Java 17 기반의 웹 서비스에서 애플리케이션 계층 간의 데이터 전송을 위해 Record를 사용하면, 코드 베이스를 간결하고 유지 관리하기 쉽게 만들 수 있습니다.
DTO와 Record의 성능을 비교할 때 차이는 크지 않지만, 몇 가지 중요한 요소들을 고려해야 합니다.
Record는 설계상 간결하므로 DTO보다 메모리를 조금 덜 사용할 수 있습니다. 그 이유는 Record가 직접 Getter, Setter, equals(), hashCode(), toString() 메서드를 구현할 필요가 없기 때문입니다. 이러한 메서드들이 Java 컴파일러에 의해 자동으로 최적화되어 생성되므로, 메모리 사용량이 줄어듭니다.
예시:
Record는 불변(immutable)이므로, 특히 멀티 스레드 환경에서 성능상의 이점을 제공합니다. Record는 불변이기 때문에 스레드 간에 공유될 때 동기화나 잠금(locking) 메커니즘이 필요하지 않습니다. 이는 스레드 간 경쟁(thread contention)으로 성능이 저하되는 상황에서 성능을 향상할 수 있습니다.
반면, 가변(mutable) DTO를 멀티 스레드 환경에서 사용할 경우, 스레드 안전성을 보장하기 위해 접근을 동기화하거나 다른 메커니즘을 사용해야 하므로 성능에 추가적인 부담이 생기고 애플리케이션이 느려질 수 있습니다.
DTO와 Record 모두 일반적인 Java 객체(POJO, Plain old Java object)이므로 동일한 가비지 컬렉션 처리에 따라 관리됩니다. 하지만 Record가 더 간결하므로 메모리에 적은 객체가 생성되거나 유지될 수 있어, 가비지 컬렉션이 조금 더 빠르게 이루어질 가능성이 있습니다. 이는 대량의 데이터 객체를 처리하는 장기 실행 애플리케이션에서 성능 향상에 기여할 수 있습니다.
Record는 컴파일러에 의해 자동 생성되며 성능을 최적화하도록 설계되어 있어, 객체 생성, 메서드 호출, 비교 작업(equals(), hashCode())에서 CPU 성능이 조금 더 향상될 수 있습니다. 특히 복잡한 DTO의 경우, 수동으로 구현된 메서드에서 비효율성이 발생할 수 있는데, Record는 일관되고 최적화된 방식으로 이러한 작업을 처리하므로 더 효율적입니다.
실제로는 DTO와 Record 간의 성능 차이는 대부분의 애플리케이션에서 매우 적거나 무시할 만한 수준일 것입니다. Record의 간결함이 특정 시나리오에서 약간의 성능 향상을 가져올 수 있지만, 실제로는 대용량 데이터 처리, 높은 처리량을 요구하는 애플리케이션, 또는 리소스가 제한된 환경(예: 모바일 또는 IoT 장치)에서만 눈에 띌 정도의 성능 차이를 경험할 수 있습니다.
지금까지 DTO와 Record의 주요 차이점, 각 사용 사례, 그리고 함수형 프로그래밍과 같은 다양한 프로그래밍 패러다임에 맞는 방법에 대해 살펴보았습니다. DTO는 유연성, 가변성, 그리고 맞춤형 동작을 제공하는 반면, Record는 데이터를 간결하고 불변하게 모델링하는 데 이상적이며, 최신 Java 애플리케이션에 적합합니다.
DTO와 Record를 선택하는 것은 결국 특정 요구 사항에 따라 달라집니다.
두 가지 접근 방식 모두 각각의 장점을 가지고 있으므로, 이를 언제 사용해야 할지 이해하면 더 효율적이고 유지보수가 쉬운 Java 코드를 작성할 수 있습니다.
<원문>
DTO vs Record in Java: Which Should You Use?
위 번역글의 원 저작권은 Ashutosh Krishna에게 있으며, 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다