결정-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 의존성 자체를 재평가합니다.
- 도메인 층에서 불변식 검사가 생성자 밖으로 옮겨질 때는 도메인 생성자도 배선이 되므로 위 표의 두 번째 행이 무의미해집니다.