기술 블로그를 둘러보다가 Java의 record 타입에 대해 알게 되었고, 흥미가 생겨 그 배경과 특징, 그리고 언제 사용하는 것이 적절한지에 대해 알아보게 되었습니다.
record란?
record는 Java에서 값을 담기 위한 목적의 클래스, 즉 DTO나 VO(Value Object)를 간결하게 선언할 수 있도록 도입된 문법입니다. Java 14부터 도입되었고, Java 16부터 정식으로 지원됩니다.
record를 사용하면 다음과 같은 요소들이 자동으로 생성됩니다.
- private final 필드
- 모든 필드를 받는 생성자
- 각 필드에 대한 getter 메서드
- toString(), equals(), hashCode() 메서드
덕분에 보일러플레이트 코드를 줄일 수 있어 DTO 작성이 훨씬 간단해집니다.
현재 진행 중인 프로젝트에서도 API마다 RequestDto, ResponseDto 클래스가 반복적으로 등장하고 있습니다. 반복적인 코드를 줄이고 DTO의 역할을 명확히 하기 위해, 특히 불변 객체의 성격이 강한 ResponseDto에 record를 도입해보기로 결정했습니다.
왜 ResponseDto에 record가 적합할까?
1. 불변성(immutability)이 자연스럽게 맞아떨어짐
- record는 모든 필드가 final로 선언되어 수정이 불가능한 구조입니다.
- ResponseDto는 서버가 클라이언트에 읽기 전용 데이터를 전달하는 용도이므로, 객체 생성 이후 변경될 일이 거의 없습니다. 따라서 record와 구조적으로 잘 맞습니다.
2. 직렬화/역직렬화에서 문제가 적음
- 대부분의 JSON 직렬화 라이브러리(예: Jackson)는 record를 잘 지원합니다.
- Spring Boot와 함께 사용할 때도 ResponseBody 변환에 별다른 문제가 없습니다.
반대로, 왜 RequestDto에는 덜 쓰일까?
1. 직렬화 라이브러리 호환성 (과거 이슈)
- Java 14~16 초기에는 일부 직렬화 라이브러리에서 record 역직렬화(@RequestBody)가 제대로 동작하지 않았습니다.
- 최신 라이브러리에서는 대부분 지원되지만, 레거시 코드나 보수적인 팀 문화에서는 여전히 꺼리는 경향이 있습니다.
2. 프레임워크 통합 시 유연성 부족
- @Valid, @NotNull 같은 validation 어노테이션은 record에도 붙일 수 있지만, 복잡한 유효성 검증이나 커스텀 생성자 처리 시 일반 클래스보다 유연성이 떨어질 수 있습니다.
3. 객체 내부 상태 조작이 필요한 경우
- RequestDto는 종종 중간 가공, 변환(예: DTO → Entity), 내부 값 수정이 필요한 경우가 있습니다.
- 하지만 record는 불변 객체이기 때문에 이런 작업에는 적합하지 않을 수 있습니다.
실제 적용 예시
예를 들어 다음과 같은 기존 ResponseDto 클래스가 있었습니다.
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TuningResponseDTO {
private Long userId;
private String profileImage;
private String nickname;
private Gender gender;
private String oneLineIntroduction;
private Map<String, String> keywords;
private Map<String, List<String>> sameInterests;
}
이 코드를 record로 다음과 같이 리팩토링했습니다.
public record TuningResponseDto(
Long userId,
String profileImage,
String nickname,
Gender gender,
String oneLineIntroduction,
Map<String, String> keywords,
Map<String, List<String>> sameInterests
) {}
그러나 하나의 예상치 못한 이슈가 있었습니다. 기존 코드에서는 DTO의 필드에 .getX() 형태로 접근하고 있었지만, 해당 부분에서 에러가 발생했습니다. 하지만 record 는 자동으로 getter를 지원하는데 왜 그럴까 생각했습니다.
그 이유는 바로 record의 필드가 nickname이라면 자동 생성되는 메서드는 nickname()이지 getNickname()이 아닙니다.
따라서 기존 코드에서 dto.getNickname()처럼 사용하던 부분을 모두 dto.nickname() 형태로 수정해야 했습니다.
회고
- record를 사용한다고 해서 기능적으로 큰 변화는 없었지만, 의도를 명확히 표현할 수 있었던 점은 분명한 장점이었습니다.
- record는 빌더 패턴을 지원하지 않기 때문에, 필드 수가 많거나 선택적 필드가 많은 경우에는 오히려 불편할 수 있습니다.
- 하지만 DTO가 단순히 값을 담아 전달하기 위한 용도라면, 클래스보다 record가 훨씬 간결하고 명시적입니다.
- 특히 ResponseDto처럼 불변이고 단방향 전달용 객체에는 매우 적합합니다.
끝.
'카카오테크 부트캠프' 카테고리의 다른 글
| MVP 이후의 코드 품질 개선 전략 — 리팩토링 (0) | 2025.06.21 |
|---|---|
| SSE 연결로 인한 HikariCP 커넥션 미반납 문제 (0) | 2025.06.14 |
| Spring Security + SSE 사용 중, 권한 문제로 Access Denied 예외가 발생 (0) | 2025.05.26 |
| boolean 필드가 JSON에서 read로 직렬화되는 이유 (0) | 2025.05.25 |
| 채팅 도메인 - SSE 도입 테크스펙 (0) | 2025.05.16 |