설정값 바인딩 안전하게 다루기

문자열 키 분산 줄이기

환경별 API 주소, 타임아웃, 기능 토글을 여러 서비스에서 @Value로 직접 읽으면 설정 이름이 코드 곳곳에 퍼진다. 값 하나를 바꿀 때도 같은 키를 쓰는 빈을 다시 찾아야 하고, 형식 오류도 늦게 드러난다.

@ConfigurationProperties는 이런 설정을 하나의 타입으로 묶는다. Spring Boot 공식 문서도 여러 속성이나 계층형 데이터는 구조화된 객체에 바인딩하는 방식을 권장한다.

설정 타입과 검증 정의하기

결제 API 설정을 예로 들면 접두사는 payment로 고정하고 필요한 값을 record 생성자 파라미터에 둔다.

@Validated
@ConfigurationProperties("payment")
public record PaymentProperties(
        @NotBlank String baseUrl,
        @NotNull Duration timeout,
        @Valid Retry retry
) {
    public record Retry(@Min(0) int maxAttempts, @NotNull Duration backoff) {}
}

@Validated는 설정값에도 검증을 적용한다. 중첩 객체에는 @Valid를 붙여 내부 필드까지 검사한다. 서비스 안에 기본값 보정 로직을 숨기지 않는 편이 좋다.

스캔과 YAML을 연결하기

@ConfigurationProperties 타입은 빈으로 등록되어야 주입할 수 있다. 패키지 전체를 관리하려면 시작 클래스에 스캔을 켠다.

@SpringBootApplication
@ConfigurationPropertiesScan
class BillingApplication {}
payment:
  base-url: https://api.example.com
  timeout: 3s
  retry:
    max-attempts: 2
    backoff: 500ms

YAML 키는 케밥 케이스로 두면 환경 변수나 프로퍼티 파일로 옮길 때도 의도가 선명하다. Duration은 3s, 500ms처럼 단위를 포함해 읽힌다.

테스트와 메타데이터까지 챙기기

서비스는 이제 키 이름 대신 PaymentProperties만 주입받는다. 설정 오류는 호출 시점이 아니라 컨텍스트 생성 시점에 드러난다. 작은 컨텍스트 테스트로 정상 바인딩을 고정해 둔다.

new ApplicationContextRunner()
        .withUserConfiguration(TestConfig.class)
        .withPropertyValues(
                "payment.base-url=https://api.example.com", "payment.timeout=2s",
                "payment.retry.max-attempts=1", "payment.retry.backoff=200ms")
        .run(context -> assertThat(context)
                .hasSingleBean(PaymentProperties.class));

IDE 자동완성까지 원한다면 빌드에 spring-boot-configuration-processor를 추가한다. 이 프로세서는 설정 타입을 읽어 메타데이터를 만든다. 설정 키는 문서, 자동완성, 테스트, 런타임 검증이 같은 타입을 바라보게 된다.

댓글