우테코

값 객체(VO) : 일반 클래스 vs record

mint* 2024. 10. 26. 18:36
728x90

값 객체(VO) : 일반 클래스 vs record

우테코 프리코스에서 코드 리뷰를 하는 과정에서, 값 객체를 구현할 때 일반 클래스와 record 중 어느 것으로 구현하는 것이 적절한지 이야기가 나왔다.
record가 보일러플레이트 코드를 줄여주고 불변 객체를 생성해주는 장점이 있어 값 객체에 적합해보이지만, 과연 도메인 객체로 사용하는 것이 적절할까?

 

record 간단 설명

  • java 16에서 정식 도입되었다.
  • 데이터 전달을 위한 불변 객체를 편리하게 생성할 수 있다.
    • 필드가 모두 private final이다. 각 필드의 이름으로 getter가 생성된다.
    • 컴파일러가 생성자와 equals(), hashCode(), toString()을 자동으로 생성한다.
  • final 클래스로 선언되므로 상속이 불가능하다.

 

예시

일반 클래스

public final class Delimiter {
    private final String value; 

    public Delimiter(String value) {
        this.value = value;
    }

    public String value() {  
        return this.value;
    }
}

 

record

public record Delimiter(String value) { }

 

record 사용하는 곳

  • DTO
  • 불변 객체
  • 단순 값 타입
  • config

 

값 객체(VO) 간단 설명

    • 도메인 개념을 추상화하여 값으로 취급한 객체
    • 불변성, 동등성, 유효성 검증 등을 보장해야 한다.
      • 불변성 : final 필드, setter 금지
      • 동등성 : 식별자가 아닌 값으로 동등성을 판단한다. - equals(), hashCode() 재정의
      • 유효성 검증: 생성 시점에 유효성을 보장한다. - validate()
로직을 캡슐화한다.

 

값 객체(VO) 구현 : 일반 클래스 vs record

캡슐화

    • 일반 클래스 : 모든 필드에 대한 접근 제어를 세밀하게 조정할 수 있다.
      • public, protected, default, private
    • record : 모든 필드가 public 접근제어자(getter)로 노출된다.
객체를 record로 구현하면 캡슐화를 보장하지 못한다.

 

 

값 객체라고 모든 필드를 공개할 필요는 없다

  • getter를 제공하지 않는 값 객체가 존재한다.
  • 내부 구현을 숨기고 필요한 동작만 공개하는 것이 좋다.
    • 아래 Password에서 value와 salt 필드 값이 노출되어서는 안된다.
public class Password {
    private final String value;
    private final String salt;  

    private Password(String value) {
        this.value = value;
        this.salt = generateSalt();  
    }

    public String maskedValue() {  
        return "*".repeat(value.length());
    }

    public boolean matches(String inputPassword) {
        return hashWithSalt(inputPassword).equals(value);
    }
}
  • record 사용시 모든 필드가 공개되어 캡슐화가 깨진다.
public record Password(String value, String salt) {
    public String getMaskedValue() {
        return "*".repeat(value.length());
    }
}

 

사용 목적

    • 일반 클래스 : 도메인 개념과 규칙을 표현한다.
    • record : 데이터 전달 및 저장을 위해 사용된다.
record는 dto처럼 데이터를 단순히 담고 있는 컨테이너 역할로 보인다.
검증을 수행하고 캡슐화된 도메인 로직을 표현하기에는 적절하지 않다.

 

기타 record 제약사항

    • 유지보수 어려움 : private 필드를 추가해야 한다면 record를 일반 클래스로 수정해야한다.
    • 실수 유발하는 코드 : record에 필드를 추가하여 실수로 캡슐화가 깨질 수 있다.
실수를 방지하는 것이 중요하므로 모든 필드가 공개되어있더라도 도메인 객체로는 사용하지 않는 것이 좋다.

 

결론

  • dto나 설정값 등 단순 데이터 전달 목적으로만 record를 사용하자.
  • 도메인을 표현하는 값 객체는 일반 클래스로 구현하자.

 

728x90