본문으로 건너뛰기

결정-0023: Lombok 사용 범위를 층에 따라 나눈다

상태

승인됨

맥락

당시 전제는 Java 21과 Spring Boot 4.1, compileOnly 로 선언된 Lombok, 그리고 컨벤션 검사기와 버그 패턴 탐지기, 포맷터가 함께 도는 구성입니다.

Lombok 은 처음부터 의존성으로 선언되어 있었지만 어디에 어떻게 쓸지는 어디에도 적혀 있지 않았습니다. 결정-0009가 DTO에 대해 record 를 쓴다고 정한 것이 전부였습니다.

그 결과 실제 코드가 이렇게 갈렸습니다. @Slf4j 는 8개 파일에서 쓰이는데 @RequiredArgsConstructor 와 @Getter, @Builder 는 한 곳도 없었습니다. 생성자 주입 클래스 28개가 전부 손으로 쓴 생성자를 갖고 있었습니다. 이것은 판단의 결과가 아니라 판단이 없어서 생긴 쏠림입니다. 파일마다 그때그때 쓴 것이 우연히 한 방향으로 몰렸습니다.

검토자가 컨트롤러를 읽다가 Lombok 을 쓰는데 왜 생성자 보일러플레이트를 억제하지 않는지를 물었고, 답을 찾으려 결정기록과 린터 설정, 작업 지침을 뒤졌으나 근거가 없었습니다. 근거가 없다는 사실 자체가 본 결정을 촉발했습니다.

작업 지침은 코딩 표준을 산문으로 쓰지 않고 린터 설정 파일이 곧 표준이라고 규정합니다. 그런데 "빈은 애노테이션, 도메인은 손으로"는 어느 린터로도 표현할 수 없습니다. 이 원칙을 문자 그대로 따르면 이런 종류의 표준은 적을 곳이 없어서 증발합니다.

검토한 대안

전면 도입

구분 내용
장점 일관되고 코드가 짧습니다
단점 도메인 객체의 생성자는 단순 대입이 아니라 불변식을 지키는 자리입니다. 애노테이션으로 바꾸면 그 검사를 넣을 곳이 사라집니다
기각 사유 계정 애그리거트의 생성자는 비밀번호 해시가 자격증명 계정에만 존재한다는 불변식을 검사합니다. 이 자리를 없앨 수 없습니다

전면 배제

구분 내용
장점 마법이 없어 읽기 쉽습니다
단점 얻는 것 없이 8개 파일에 상용구 한 줄씩을 되돌리는 것이고, Spring 빈의 생성자도 계속 손으로 관리해야 합니다
기각 사유 비용만 있고 이득이 없습니다

결정

층에 따라 나눕니다. 기준은 그 생성자가 하는 일이 배선인가, 규칙의 집행인가입니다.

대상 방식 근거
Spring 빈 (컨트롤러 · 서비스 · 설정 · 저장소 어댑터) @RequiredArgsConstructor 생성자가 순수 배선입니다. 넣을 로직이 없습니다
도메인 객체 (애그리거트 · 값 객체) 손으로 작성합니다 생성자가 불변식을 지키는 자리입니다. 없애면 그 자리가 사라집니다
DTO · 요청과 응답 · 결과 객체 record 결정-0009가 이미 정한 것입니다
로거 @Slf4j 현행을 유지합니다

@Getter 와 @Setter, @Data, @Builder 는 쓰지 않습니다. 도메인 객체의 접근자는 손으로 씁니다. 어떤 필드를 밖에 내보낼지가 설계 판단인데 @Getter 는 그 판단을 전부로 획일화합니다. DTO는 record 라 접근자가 자동으로 생기므로 애초에 필요가 없습니다.

주의

@RequiredArgsConstructor 는 필드 선언 순서대로 생성자 파라미터를 만듭니다

나중에 필드 순서를 바꾸면 생성자 시그니처가 조용히 바뀝니다. 타입이 서로 다르면 컴파일러가 잡지만 같은 타입이 둘 이상이면 못 잡습니다. 테스트가 생성자를 직접 부르는 곳이 있으므로 실재하는 위험입니다. 이 위험을 감수하는 이유는 그런 클래스가 Spring 빈 층에 한정되고 거기서는 같은 타입 의존성이 둘 이상 오는 경우가 드물기 때문입니다. 같은 타입 의존성이 둘 이상인 빈은 손으로 생성자를 씁니다. 위 표에서 벗어나는 유일한 예외입니다.

결과

  • Spring 빈 26개에서 생성자 상용구가 사라져 150줄이 줄었습니다. 의존성을 추가할 때 필드와 생성자를 따로 고칠 일이 없습니다. 파라미터 개수 경고를 억제하던 주석도 함께 사라졌습니다. 생성자가 소스에 없으므로 검사기가 셀 것이 없기 때문입니다.
  • 도메인 객체의 생성자는 그대로 남아 여기에 규칙이 있다는 신호가 유지됩니다. 애노테이션이 붙은 클래스와 붙지 않은 클래스의 차이 자체가 층을 드러냅니다.
  • 감수하는 것은 위에 적은 필드 순서 위험과 린터가 이 표를 강제하지 못한다는 것입니다. 본 결정은 검토로만 지켜집니다.
  • 예외로 손에 남긴 곳 두 군데에는 그 이유를 코드 주석으로 달았습니다. 린터가 강제하지 못하므로 다음 사람이 여기만 안 바뀌었다며 마저 바꾸는 것을 막을 근거가 코드 안에 있어야 합니다. 하나는 마지막 줄이 타이밍 균등화 해시를 계산하는 세션 서비스이고, 다른 하나는 받은 설정값을 담지 않고 그것으로 키 명세를 만들어 담는 해셔입니다.

함께 고친 것

본 결정이 갈 곳이 없었다는 문제를 함께 고쳤습니다. 작업 지침의 문서 원칙에 린터 설정으로 표현할 수 없는 코딩 표준은 결정기록에 둔다를 추가했습니다. 이 한 줄이 없으면 린터가 곧 표준이라는 원칙이 린터로 표현 못 하는 표준을 침묵시킵니다.

재검토 조건

  • Java 가 언어 차원으로 이 상용구를 없앨 때 Lombok 의존성 자체를 재평가합니다.
  • 도메인 층에서 불변식 검사가 생성자 밖으로 옮겨질 때는 도메인 생성자도 배선이 되므로 위 표의 두 번째 행이 무의미해집니다.
© 2026 dev.goraebap