[Java - RECORD] , DTO를 record type으로 설정하는 이유

오영선·2023년 4월 25일
1

스프링 프로젝트를 기획하면서 DTO를 만드는 과정에서, class를 record로 바꿀 수 있다는 것을 알게되었다.

Record

  • 값의 집합으로 이루어진 간단한 객체를 심플하게 개발하기위해 고안되었습니다.
  • immutable data(불변데이터)를 다룰 class 구현에 최적화되어있습니다.
  • 웹서비스 구현에서 많이 사용되는 DTO 개념에 적용하면 유용합니다.
  • 생성자, accessors(getter), equals(), hashCode(), toString() 등 DTO 특성의 클래스를 개발할때 매번 개발자가 직접 구현해주어야 했던 반복적인 작업이 불필요하게 됩니다.
  • 또한, "이 클래스는 data carrier 용도이다"를 명시적으로 나타낼 수 있으므로 코드에 대한 이해를 돕고 해당 클래스의 목적에 맞지 않는 구현을 하지 않도록 방지합니다.

참조 블로그

Record 클래스 특징

  • final 클래스이므로 다른 클래스를 상속하거나/상속시킬 수 없습니다.
  • 자동생성 accessor 함수는 인스턴스 멤버 변수의 이름과 동일합니다.
  • Record 클래스의 접근제어자는 public, package-private 만 가능합니다.
  • Record 생성자의 접근제어자는 클래스의 접근제어자보다 제한된 수준이어서는 안됩니다.

DTO 클래스에 record클래스가 적합한 이유

  • 단순하게 데이터를 한쪽에서 다른 한쪽으로 전달하기 위해서만 사용되는 데이터 전송 객체(혹은 DTO)를 생각해봅시다.

이런 객체를 사용하는 이유는 다양한 집계 연산을 수행한 후의 결과를 담아두거나, 외부 시스템과 통신 시에 필요하지 않은 데이터를 제거하여 대역폭 사용량을 줄이기 위해, 세부 구현을 노출시키지 않기 위해서, 혹은 변경되지 말아야 하는 API 설계 상의 이유 등 다양한 이유가 있을 수 있습니다.

  • 이를 제대로 구현하기 위해서는 (롬복이나 IDE의 도움을 받을 수도 있지만) 아래와 같이 게터(getter 혹은 accessor), equals(), hashCode(), toString() 처럼 계속 똑같은 구조의 코드를 반복해서 작성해야 했습니다.
    참조 블로그

우리의 경우에는 lombok의 @Getter를 통해 대체하고 있지만..


@Getter
@NoArgsConstructor
public class PassDto {

    Long id;

    String name;

    Integer count;

레코드 선언방법

record 레코드명(컴포넌트1, 컴포넌트2, ...) { }

실제 코드에 적용된 내용

적용 전 (PassDto.class)

package com.haedal.entity;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;

import javax.persistence.Column;
import java.time.LocalDateTime;

@Getter
@NoArgsConstructor
public class PassDto {

    Long id;

    String name;

    Integer count;

    Integer price;

    LocalDateTime startedDay;

    LocalDateTime endedDay;

    public PassDto(Long id, String name, Integer count, Integer price, LocalDateTime startedDay, LocalDateTime endedDay) {
        this.id = id;
        this.name =name;
        this.count = count;
        this.price = price;
        this.startedDay = startedDay;
        this.endedDay = endedDay;
    }

    public static PassDto of(Long id, String name, Integer count, Integer price, LocalDateTime startedDay, LocalDateTime endedDay) {
        return new PassDto(id, name, count, price, startedDay, endedDay);
    }

    public static PassDto from(Pass pass){
        return new PassDto(
                pass.getPassId(),
                pass.getName(),
                pass.getCount(),
                pass.getPrice(),
                pass.getStartedDay(),
                pass.getEndedDay()
        );
    }


    public Pass toEntity() {
        return Pass.builder()
                .passId(id)
                .name(name)
                .count(count)
                .price(price)
                .startedDay(startedDay)
                .endedDay(endedDay)
                .build();
    }
}

적용 후(PassDto.record)


//Getter, 생성자에 관련된 어노테이션이 사라졌다.
public record PassDto(
        Long id,
        String name,
        Integer count,
        Integer price,
        LocalDateTime startedDay,
        LocalDateTime endedDay
) {
    
    public static PassDto of(Long id, String name, Integer count, Integer price, LocalDateTime startedDay, LocalDateTime endedDay) {
        return new PassDto(id, name, count, price, startedDay, endedDay);
    }

    public static PassDto from(Pass pass){
        return new PassDto(
                pass.getPassId(),
                pass.getName(),
                pass.getCount(),
                pass.getPrice(),
                pass.getStartedDay(),
                pass.getEndedDay()
        );
    }


    public Pass toEntity() {
        return Pass.builder()
                .passId(id)
                .name(name)
                .count(count)
                .price(price)
                .startedDay(startedDay)
                .endedDay(endedDay)
                .build();
    }
}

0개의 댓글