본문으로 건너뛰기

결정-0009: DTO 와 값 객체에 Java record 를 허용하되 가변 컴포넌트는 방어복사를 의무화한다

상태

승인됨

맥락

선례로 삼은 팀 프로젝트는 DTO와 값 객체를 record 가 아닌 Lombok class 로 통일하는 규칙을 운영합니다. 근거는 버그 패턴 탐지기의 가변 객체 참조 노출 경고를 회피하는 것과 팀 컨벤션 일치였습니다. 본 시스템도 같은 탐지기를 쓰므로(결정-0056) 이 규칙을 승계할지 판단해야 했습니다.

한편 jOOQ 는 생성자 기반 매핑으로 record 프로젝션을 1급 지원하고, 도메인 모델과 생성 레코드가 분리되는 구조(결정-0051)에서 DTO는 대부분 불변 값입니다. record 를 금지하면 보일러플레이트가 늘고 조회 결과 매핑에 변환 단계가 추가됩니다.

검증

기억이나 문헌이 아니라 이 환경에서 직접 측정했습니다. SpotBugs 4.10.3과 Java 21 에서 2026-07-24에 수행한 결과입니다.

프로브 결과
record(String, List, Map, byte[]) — 방어복사 없음 EI_EXPOSE_REP 4건
record(String, int, UUID, Instant, List) — 컴팩트 생성자에서 List.copyOf 경고 0건

결론은 버그 패턴 탐지기와 record 가 근본적으로 충돌하지 않는다는 것입니다. 경고는 가변 컴포넌트를 방어복사 없이 노출할 때만 발생하며, 이는 오탐이 아니라 외부에서 내부 상태를 바꿀 수 있다는 실제 결함입니다. 컴팩트 생성자 한 줄로 해소되고 그 결과 진짜 불변성이 확보됩니다.

class 전환이 이 경고를 없애는 이유는 문제를 고쳐서가 아니라 Lombok 생성 메서드가 분석에서 제외되기 때문일 가능성이 높습니다. 경고를 가리는 쪽에 가깝습니다.

검토한 대안

record 를 금지하고 Lombok class 로 통일합니다

구분 내용
장점 선례를 그대로 승계해 판단 비용이 없습니다
단점 경고를 가릴 뿐 불변성 문제는 남습니다. 보일러플레이트와 매핑 변환 비용이 듭니다
기각 사유 위 측정이 경고의 원인을 실제 결함으로 특정했습니다

record 를 허용하되 탐지기 제외 필터로 경고를 억제합니다

구분 내용
장점 방어복사를 쓰지 않아도 됩니다
단점 실제 결함까지 함께 눈감게 됩니다
기각 사유 측정 결과 억제가 불필요했습니다. 불필요한 억제는 게이트의 신뢰를 깎습니다

record 를 허용하고 방어복사를 의무화합니다 (채택)

설정 변경 없이 경고가 0건이며 진짜 불변성을 확보합니다.

결정

  • DTO와 값 객체, 조회 프로젝션 타깃은 record 를 기본으로 합니다.
  • 가변 컴포넌트를 가지는 record 는 컴팩트 생성자에서 방어복사합니다. 컬렉션과 배열, 가변 객체가 그 대상입니다. 버그 패턴 탐지기가 이를 강제하므로 별도 리뷰 항목으로 두지 않습니다.
  • 탐지기 제외 필터에 가변 객체 참조 노출 규칙을 추가하지 않습니다.
  • 역직렬화가 필요한 요청 DTO도 record 로 씁니다. 프레임워크 제약으로 불가능한 사례가 나오면 그때 class 를 쓰고 사유를 주석으로 남깁니다.

결과

  • 조회 결과를 jOOQ 에서 DTO로 바로 프로젝션할 수 있어 변환 코드가 줄어듭니다.
  • 정적 분석 설정을 느슨하게 만들지 않고도 record 를 씁니다. 게이트의 신뢰가 유지됩니다.
  • 감수하는 것은 컬렉션을 가진 record 마다 붙는 컴팩트 생성자 한 줄입니다.
© 2026 dev.goraebap