이번에 자바 버전을 21버전으로 마이그레이션 하면서, 기존에 사용하던 11버전과의 가장 큰 차이는 역시 Record의 도입이 아닐까 싶다. Record에 관한 자세한 설명은 밸덩의 아티클을 참조하면 좋을 것이다. 이번 포스트에서는 간단한 Record의 특징과 리팩토링에 어떻게 적용했는지를 다룰 예정이다.
Record의 특징
Record의 가장 큰 특징이라면 모든 필드가 private final로 강제 선언되며, 해당 필드에 대한 getter가 주어진다는 것이다. 또한, equals(), hashCode(), toString()의 Object기본 메서드들이 자동으로 생성된다. 이러한 특징들을 바탕으로 어떤 형태의 Object들에 Record를 적용해보면 좋을지 고민해 보았다.
Record는 어디에 쓰면 좋을까?
가장 먼저 떠오른 활용처는 DTO였다. DTO는 다른 계층에서 사용해야 하는 데이터를 전달하기 때문에 데이터가 일부 누락된 불완전한 상태로 사용하지 않는 것이 좋다고 생각해 원래도 모든 필드를 private final로 선언했고, 값을 수정하지 않았다. 수정에 사용되는 데이터도 DTO 내부에서 수정될 일은 없다. 애초에 Data Transfer Object다. 데이터를 전송만 하면 되지, 내부에서 값을 수정한다거나 하는 일은 DTO가 할 일이 아니다. 또한, 여러 데이터를 DTO로 반환하는 경우에 만약 중복 데이터를 제거해야 한다면, Record에서 제공하는 equals()와 hashCode()가 유용하게 사용될 것이었다. 따라서 DTO에는 Record를 적용했다.
다음으로 적용을 고민한 영역은 VO였다. VO는 DTO와 비슷하게 구성되어 있다. 특히 나는 더욱 그랬는데, 나는 DTO의 모든 필드를 private final로 선언했으니 더욱 VO와 유사했다. 또한, VO는 값을 표현한다는 그 특성상 반드시 equals()와 hashCode()를 구현해야 레퍼런스 체크와 identification이 가능하다. 또한, 마찬가지로 값의 수정을 허용하지 않는다. Record를 적용하지 않을 이유가 없었다.
리팩토링 예시
예시 코드로는 DTO와 VO를 각각 하나씩 가져오려 한다. 먼저 VO의 경우다.
@Getter
public final class MemberVO {
private final Long memberId;
private MemberVO(Long memberId) {
this.memberId = memberId;
}
public static MemberVO of(Long memberId){
return new MemberVO(memberId);
}
@Override
public boolean equals(Object obj) {
if(obj instanceof MemberVO memberVO){
return this.memberId.equals(memberVO.memberId);
}
return false;
}
@Override
public int hashCode() {
return memberId.hashCode();
}
}
회원 VO에는 회원 id만 사용했다. 왜 id만 썼는지에 대해서는 다음에 기회가 되면 다시 포스팅해보면 좋을 것 같다(핵심은 수정되면 안되는 값만 들어가야 한다는 점에 있다). 아무튼 위의 코드를 record를 이용하면 아래와 같이 개선된다.
public record MemberVO(Long memberId) {
}
물론 정적 팩터리 메서드를 없애긴 했다만, 코드가 단 한 줄로 정리된다. 그럼에도 위의 코드와 같은 기능을 한다.
다음으로 DTO의 경우를 살펴보자.
그냥 커밋 내역을 가져왔다. 정적 팩터리 메서드를 저렇게 쓸거면 그냥 생성자를 사용하는게 나을 것 같다. 아무튼, 그렇게만 사용해도 생성자 코드를 제거할 수 있다. lombok을 사용할 필요도 없어졌다. 필드를 여러 줄로 나눠놓은 건 최근 kotlin으로 기술 과제를 수행하다보니 저 편이 더 가독성이 높은 것 같아 저렇게 사용했다.
마치며
DTO에는 사실 큰 변화는 없고, 대신 VO에 좀 변화가 생겼다. 아무튼 Record를 사용하는 이유를 알아보고 적용하는 시간을 가졌다. 불변 클래스를 강제로 만들어주고, 필요한 공통 메서드를 자동으로 재정의해준다는 점은 Record를 사용하는 큰 이유다. 반면, 불변 클래스이기 때문에 수정이 불가능하며, 공통 메서드를 자동으로 재정의하기 때문에 필요한 부분만 사용하여 커스텀하기 어렵다는 점은 Record가 지닌 단점일 것이다. 이러한 점을 잘 구분하고 판단하여 사용하도록 하자.
'Programming > TDD Project' 카테고리의 다른 글
Pull Request 030. 바버샵 등록 기능 구현하기 - Slack WebHook 연동 및 비동기 이벤트 처리 해보기 + 계층 간 통신 고민하기 (0) | 2024.05.20 |
---|---|
Pull Request 028. Spring Security + OAuth2.0 로그인 적용하기 (0) | 2024.01.23 |
Pull Request 027. 로그인 / 로그아웃 로직 리팩토링하기 - Http Header 대신 Cookie를 사용해보자 (0) | 2024.01.09 |
Pull Request 026. 자유 게시글 테스트 코드를 리팩토링하며 느낀 점 (0) | 2023.12.29 |
Pull Request 025. 회원 도메인을 향한 의존은 어디에 존재해야 할까? - 게시글 수정 기능 리팩토링 (0) | 2023.12.18 |
댓글