record vs 일반 class(Spring Security CustomUserPrincipal)

최정윤·2026년 8월 19일

Spring

목록 보기
32/43

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

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


1. 핵심 원리

CustomUserPrincipal은 memberId · 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개의 댓글