record vs 일반 class(Spring Security CustomUserPrincipal)

최정윤·2026년 8월 19일

Spring

목록 보기
32/38

JWT 인증에서 로그인 사용자 정보를 담는 CustomUserPrincipal
record로 만들지 class로 만들지 고민했다.

이번 과정의 핵심은 "record가 더 짧다"가 아니라,
"record의 접근자 규칙이 기존 팀 코드와 맞는가"를 이해하는 것이었다.


1. 핵심 원리

CustomUserPrincipalmemberId · email · role을 담아 전달만 하는 불변 값 객체다.
이런 용도엔 record가 어울린다. record 한 줄이면 아래가 전부 자동 생성된다.

public record CustomUserPrincipal(Long memberId, String email, String role) { }
record가 자동 생성하는 것내용
필드private final memberId, email, role
생성자세 값을 받는 canonical 생성자
접근자memberId(), email(), role()get 접두어 없음
부가 메서드equals(), hashCode(), toString()

핵심은 접근자 이름이다. record는 getMemberId()가 아니라 memberId()를 만든다.


2. 기술적 의사결정 — 왜 record 대신 class를 택했나

우리 팀 코드는 전부 JavaBean 관례(getXxx())로 principal을 호출하고 있었다.

// MemberController
memberService.getMe(principal.getMemberId());

// OrderController (여러 곳)
orderService.createOrder(userPrincipal.getMemberId(), request);
orderService.cancelOrder(userPrincipal.getMemberId(), orderId, request);

record로 만들면 이 호출들이 전부 getMemberId()를 찾지 못해 깨진다.
그래서 다음 이유로 final 필드 + @Getter 일반 class를 택했다.

  • 팀 전체가 쓰는 getXxx() 관례와 호환되어야 하므로
  • 그러면서 final 필드로 record의 장점인 불변성은 그대로 유지할 수 있으므로
  • record에 getMemberId()를 따로 또 만들면 record를 쓰는 의미가 없어지므로
@Getter
public class CustomUserPrincipal {

    private final Long memberId;
    private final String email;
    private final String role;

    public CustomUserPrincipal(Long memberId, String email, String role) {
        this.memberId = memberId;
        this.email = email;
        this.role = role;
    }
}

3. 트러블슈팅

문제원인해결
principal.getMemberId() 컴파일 에러record의 접근자는 memberId() (get 없음)@Getter 붙인 class로 전환해 getMemberId() 생성
record인데 값이 안 바뀌길 원함(record는 기본 불변)class에서도 필드를 final로 두어 동일하게 불변 확보
getter를 일일이 쓰기 번거로움class는 getter 수동 작성 필요Lombok @Getter로 boilerplate 제거

record가 더 세련됐다고 무조건 좋은 게 아니라,
주변 코드와의 호환을 함께 봐야 한다
는 점을 실제로 부딪혀 배웠다.


4. 실무 활용

  • record가 유리한 경우: 외부 관례에 얽매이지 않는 순수 값 운반 —
    DTO, 좌표·범위 같은 값 객체, 다른 클래스를 상속할 필요 없는 불변 데이터.
  • class(+@Getter)가 유리한 경우: 기존 코드가 getXxx() JavaBean 관례를 쓰거나,
    상속·프레임워크 연동 등 유연성이 필요한 경우.
  • 불변성은 record만의 것이 아니다. class도 final 필드로 동일하게 만들 수 있다.

5. 마무리

record는 불변 데이터 운반용으로 훌륭하고 코드를 크게 줄여준다.
하지만 접근자가 getX()가 아니라 x()라서, 이미 getX() 관례로 짜인
팀 코드와는 충돌한다. 이번엔 여러 컨트롤러가 getMemberId()로 호출하는
공용 객체였기 때문에 final 필드 + @Getter class가 정답이었다.

한 줄 요약

record는 불변 값 객체엔 최고지만 접근자가 x()다.
팀이 getX()를 쓰면 안 맞으므로, 공용 CustomUserPrincipal
final 필드 + @Getter 클래스로 불변성과 관례를 모두 챙겼다.

profile
콩떡

0개의 댓글