DTO를 확 줄인 Record

Letsdev·2023년 5월 14일
4

Prev >
Java 16 - Record Class (1) Easy Syntax
Next >
Java 16 Record 사용처: 속성 파일을 쉽게 객체화한 Configuration Properties - @Value보다 ...

Record의 불변성을 설명하는 그림으로, 뒤에 "CANNOt MODIFY"라는 플래카드가 붙어 있고, 두 졸라맨 중 첫 번째 대사가 "Can I record again?", 두 번째 대사가 "with new record."라고 적혀 있는 그림. "다시 기록해도 돼?", "음, 새 레포드판을 쓴다면." 정도 대화로 이해할 수 있다.

Record는

앞글에서 말한 대로 record는 불변 객체를 만들라고 나온 쉬운 클래스 유형이다.

사용이 쉬운 덕분에 DTO 작성 때 딱히 뭘 안 하고 필드만 나열하면 알아서 final로 인식되고 getter도 toString도 생성자도 equals도 hashCode도 implements Serializable도 알아서 생겨서 편하게 쓸 수 있다.

DTO에 사용

가장 대표적인 사용처다.

Code First

@Builder
public record SignInRequestDto(
        @Pattern(regexp = "...", message = "...")
        String username,
        
        @JsonProperty("password")
        @Pattern(regexp = "...", message = "...")
        String rawPassword
) {}

@Builder
public record OauthSignInRequestDto(
        @NotBlank String code,
        String state,
        @NotNull AuthType provider
) {}

public enum AuthType {
	@JsonProperty("kakao") KAKAO,
	@JsonProperty("naver") NAVER,
	@JsonProperty("google") GOOGLE,
	@JsonProperty("apple") APPLE,
	@JsonProperty("native") NATIVE
}
// Response DTO
@Builder
public record MemberOAuthSignInResponseDto(
        @JsonInclude(Include.NON_EMPTY)
        String signUpCode,
        
        @JsonInclude(Include.NON_EMPTY)
        String email,
        
        @JsonInclude(Include.USE_DEFAULTS)
        Boolean isRegistered,
        
        @JsonInclude(Include.NON_DEFAULT)
        Boolean hasNativeAccount,
        
        @JsonInclude(Include.NON_DEFAULT)
        Boolean requires2Fa
) {}

컴팩트 생성자

우리가 record에서 관심을 기울일 생성자 두 유형은 다음과 같다.

  • All Args Constructor (모든 필드를 사용)
  • 컴팩트 생성자

이중에서 All Args Constructor는 기본 생성이 된다.
그리고 이때 생성자에 온 파라미터를 확인하는 곳이 Compact Constructor다.

컴팩트 생성자는 public 레코드클래스이름 {} 형태로 사용한다.

public record SampleRecord(String name) {
    // Compact Constructor
    public SampleProperties {
        if (name == null || name.isBlank()) {
            name = "noname"; // 기본값을 주거나 예외를 발생시킬 수 있다.
        }
        
        name = name.strip(); // 값을 전처리할 수 있다.
    }
}

이때 namethis.name이 아니라 파라미터 name이다. (생성자 파라미터 검색)
thisthis.name은 컴팩트 생성자 안에서 사용하면 안 된다. (아직 실제 생성 전이기 때문.)

정보를 요약하면 다음과 같다.

  • 컴팩트 생성자 안에서 매개 변수는 아직 final이 아니다.
  • 컴팩트 생성자 안에 this, this.name 등을 사용하면 안 된다.

Note 1: Builder를 쓰자

나중에 데이터 스펙이 얼마든지 바뀔 수 있기 때문이다.

Record는 All Arguments Constructor를 자동으로 생성하는 놈이기 때문에, 나중에 필드 종류를 바꾸거나 순서만 바꿔도, 얘의 생성자가 쓰이고 있는 모든 곳을 수정하거나, 매번 생성자를 추가해서 새로운 All Args Constructor에게 전달해야 한다.

그럴 바엔 빌더랑, 필요에 따라 컴팩트 생성자로 관리하는 게 훨씬 편하다.

Note 2: 래퍼 클래스를 쓰자

일반적으로 래퍼 사용이 유리할 것이다.
Nullable에 대한 명시를 하면 좋겠다는 의미다.

다른 생성자 사용

레코드 클래스를 통해 생성하는 객체는 반드시 All Args Constructor를 통하도록 만들어야 한다.

public record MyRecord(String name, Integer age) {
    public MyRecord(String name, Instant birth) {
        // this(...)로 All Args Constructor 호출
        this(name, calculateAge(birth));
    }
    
    private static int calculateAge(Instant birth) {
        // ...
    }
}

유념할 것은 자바는 전통적인 언어라서, super(), this() 등 다른 생성자를 사용할 땐 반드시 생성자 실행문 첫 줄에 사용해야 한다는 것이다. (주석 제외한 첫 줄)

그래서 어떤 연산이 필요하다면 위 예시처럼 다른 함수를 통해야 한다.

profile
아 성장판 쑤셔 (블로그 이전) https://letsdev.hashnode.dev

0개의 댓글