Record는 Java 16에서 정식 출시된 특별한 유형의 클래스로 불변성을 기본으로 한다.
기존의 클래스와 달리 모든 필드가 final 키워드로 선언되며, 객체 생성 후 변경할 수 없다. 또한 필드 선언만으로 자동으로 생성자, getter, equals(), hashCode(), toString() 등 메서드를 자동으로 생성해 주어 보일러 플레이트 코드를 줄일 수 있다. 이러한 특성으로 인해 멀티 스레드 환경에서 데이터가 의도치 않게 변경되지 않고 안전하게 전달할 수 있다.
public class MemberDto {
private final String name;
private final String email;
private final String gender;
public MemberDto(String name, String email, String gender) {
this.name = name;
this.email = email;
this.gender = gender;
}
public String getName() {
return name;
}
public String getEamil() {
return email;
}
public String getGender() {
return gender;
}
// Record. 생성자, getter, hashCode(), equals(), toString() 자동 완성
public record MemberDto(String name, String email, String gender) {}
모든 Record 객체가 DTO인 것은 아니다. Record는 단순히 데이터를 캡슐화하는 역할을 하는데, DTO외에도 값 객체 (Value Objects) 등의 다양한 용도로 사용될 수 있다.
// 값 객체로 사용
public record Coordinates(double x, double y) {}
DTO는 계층 간 데이터 전송을 목적으로 하는 객체이고, VO는 도메인 모델 내에서 특정 값을 표현하는 객체로 사용된다. 따라서, Record는 이 두 가지 모두에 적합하게 사용할 수 있다.
Record와 VO는 모두 객체의 상태가 변경되지 않는 것을 보장한다. 또 데이터를 캡슐화하여 표현하는 데 초점을 맞춘다. 마지막으로 VO는 값 기반의 동등성을 가지며, Record도 동일한 필드 값을 가지면 동일한 객체로 간주된다는 점이 공통점이다.
VO는 도메인 모델 내에서 특정 개념을 표현하고, 도메인 로직과 밀접하게 관련이 있다. 즉, VO는 비즈니스 로직이나 규칙을 가질 수 있다. 반면에 Record는 단순히 데이터를 캡슐화하여 저장하는데 의미가 있다.
결론적으로, Record는 VO를 구현하는 데 적합하지만, VO의 모든 특성을 완벽히 대체하지는 않는다. VO는 더 넓은 도메인 맥락에서 사용되며, 비즈니스 로직을 포함할 수 있다.
Record는 extends를 사용하여 다른 클래스를 상속할 수 없고, 필드가 final로 선언되기 때문에 확장이 어렵다. 또 주로 데이터를 전달하려는 목적으로 설계되었기 때문에 비즈니스 로직을 포함하기에 적절하지 않다. 마지막으로 Java 14 또는 16 이전 버전에 호환이 불가능하다.
불변성
Record는 기본적으로 불변성을 가지며, 한 번 생성된 Record 인스턴스의 데이터는 변경할 수 없다. 이러한 불변성 덕분에 데이터는 일관성을 유지하며, 추가적인 코드 없이도 스레드 안전(Threrad-Safe)하다.
반면 DTO는 일반적으로 가변성을 가지며, 객체가 생성된 후에도 필드를 변경할 수 있다. DTO를 불변으로 만들려면 Setter 메서드를 사용하지 않거나, final 필드를 사용하여 신중하게 설계해야 한다.
보일러 플레이트 코드
Record의 큰 장점 중 하나는 보일러 플레이트 코드, 즉 반복적인 코드를 크게 줄일 수 있다는 점이다. DTO를 사용할 때는 보통 Getter, Setter, 생성자, equals(), hashCode(), toString() 메서드를 직접 작성해야 하지만, Record는 이러한 메서드를 자동으로 생성한다.
반대로, DTO는 직접 코드를 작성해야 한다. 롬복 같은 도구를 사용하면 보일러 플레이트 코드를 줄일 수 있지만, Record만큼 간단하지 않다.
데이터 표현 방식
Record는 데이터를 간결하고 직관적으로 표현하는 방법을 제공한다. Record 선언에는 필드만 포함되므로 코드가 더 깔끔하고 읽기 쉽다. 특히 데이터 모델이 많은 프로젝트에서 유지보수가 용이하다.
커스터마이징
DTO의 장점 중 하나는 커스터마이징이 용이하다는 점이다. DTO에서는 데이터 유효성 검사, 데이터 변환 메서드 또는 비즈니스 로직을 추가할 수 있다.
반면, Record는 커스터마이징이 제한적이다. Record는 가볍고 불변성을 유지하도록 설계되었기 때문에 내부 상태를 수정하거나 복잡한 로직을 쉽게 추가할 수 없다. 만약 데이터 객체에 커스터마이징된 동작 로직이 필요하다면 DTO가 더 유연한 선택이다.
함수형 프로그래밍과의 연관성
함수형 프로그래밍의 핵심 원칙 중 하나는 불변성(Immutability)입니다. 즉, 데이터 객체가 한 번 생성된 후에는 변경되지 않아야 한다는 것이다. Record는 기본적으로 불변이므로 함수형 프로그래밍 원칙과 잘 맞는다. 따라서 불변 데이터 객체를 사용하고자 하는 시스템에 적합한 선택이다.
반면 DTO는 가변성을 가지며, 불변으로 만들려면 수동적인 설정이 필요하다. DTO는 상태 변경이 흔한 객체 지향 프로그래밍 스타일에 더 적합하다.
DTO를 사용해야 할 때
데이터 수정이 필요한 경우
객체의 데이터를 생성 후 수정해야 할 때는 DTO가 더 적합하다. DTO는 보통 가변적이어서 필드 값을 필요에 따라 변경할 수 있다. 객체의 수명 주기 동안 데이터가 계속해서 업데이트 되는 상황에서 유용하다.
추가적인 동작이나 검증 로직이 필요한 경우
DTO는 검증, 변환, 또는 추가 메서드 등 맞춤형 동작을 추가하는데 더 유연하다. 데이터 객체가 단순히 데이터를 전달하는 것 이상으로 동작해야 할 때 적합하다.
이전 버전의 Java(16 이전 버전)와 호환이 필요한 경우
Java 16 이전 버전을 사용하는 프로젝트라면 Record를 사용할 수 없다.
Record를 사용해야 할 때
간결하고 불변성을 가진 데이터 전달 객체가 필요한 경우
Record는 가볍고 불변성을 가진 객체로 데이터를 전달해야 할 때 이상적이다. Record는필수적인 메서드들을 자동으로 생성해 주기 때문에 데이터 표현을 간결하고 효율적으로 처리할 수 있다.
예시 ) MSA에서 서비스 간 데이터를 전달할 때 데이터를 수정할 필요가 없다면 Record가 완벽한 선택일 수 있다.
읽기 전용 데이터 전송이 필요한 경우
애플리케이션에서 데이터를 전달하기만 하고 수정할 필요가 없다면 Record를 사용하는 것이 좋다. Record는 불변성을 보장하여 데이터 일관성을 유지하기 때문에, 데이터베이스에서 서비스 계층으로 또는 서비스 간 데이터를 전달하는데 적합하다.
최신 자바 애플리케이션에서
Java 16 이상을 사용하고 있다면 Record를 충분히 활용할 수 있다. Record는 최신 Java 애플리케이션에서 데이터 표현을 간소화하도록 설계되었으며, 기존 DTO가 가지고 있던 불필요한 반복적인 코드를 줄이는 데 도움이 된다.
DTO와 Record의 성능을 비교할 때 차이는 크지 않지만, 몇 가지 중요한 요소들을 고려해야 한다.
메모리 효율성
Record는 설계상 간결하므로 DTO보다 메모리를 조금 덜 사용할 수 있다. 그 이유는 Record가 직접 Getter, Setter, equals(), hashCode(), toString() 같은 메서드를 구현할 필요가 없기 때문이다. 이러한 메서드들이 Java 컴파일러에 의해 자동으로 최적화 되어 생성되므로 , 메모리 사용량이 줄어든다.
불변성과 스레드 안정성
Record는 불변이므로 특히 멀티 스레드 환경에서 성능상의 이점을 제공한다. Record는 불변이기 때문에 스레드 간에 공유될 때 동기화나 잠금(locking) 메커니즘이 필요하지 않다. 이는 스레드 간 경쟁으로 성능이 저하되는 상황에서 성능을 향상할 수 있다.
반면, 가변 DTO를 멀티 스레드 환경에서 사용할 경우, 스레드 안정성을 보장하기 위해 접근을 동기화하거나 다른 메커니즘을 사용해야 하므로 추가적인 부담이 생기고 애플리케이션이 느려질 수 있다.
가비지 컬렉션
DTO와 Record 모두 일반적인 Java 객체이므로 동일한 가비지 컬렉션 처리에 따라 관리된다. 하지만 Record가 더 간결하므로 메모리에 적은 객체가 생성되거나 유지될 수 있어, 가비지 컬렉션이 조금 더 빠르게 이루어질 가능성이 있다. 이는 대량의 데이터 객체를 처리하는 장기 실행 애플리케이션에서 성능 향상에 기여할 수 있다.
CPU 오버헤드
Record는 컴파일러에 의해 자동 생성되며 성능을 최적화하도록 설계되어 있어, 객체 생성, 메서드 호출, 비교 작업에서 CPU 성능이 조금 더 향상될 수 있다. 특히 복잡한 DTO의 경우, 수동으로 구현된 메서드에서 비 효율성이 발생할 수 있는데, Record는 일관되고 최적화된 방식으로 이러한 작업을 처리하므로 효율적이다.
실제 성능
실제로는 DTO와 Record 간의 성능 차이는 대부분의 애플리케이션에서 매우 적거나 무시할 만한 수준일 것이다. Record의 간결함이 특정 시나리오에서 약간의 성능 향상을 가져올 수 있지만, 실제로는 대용량 데이터 처리, 높은 처리량을 요구하는 애플리케이션, 또는 리소스가 제한된 환경(예시 : 모바일 또는 IoT 장치)에서만 눈에 띌 정도의 성능 차이를 경험할 수 있다.
오늘도 좋은 글 감사합니다 ^^😊