이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.
- Boot는 코드 밖의 설정 값을 어떤 우선순위로 읽어 애플리케이션에 주입하는가?
@Value와@ConfigurationProperties는 어떤 기준으로 선택해야 하는가?- Profile을 환경 분리 도구로 쓸 때 생기는 장점과 위험은 무엇인가?
개요
Spring Boot 설정은 코드 밖에서 들어온다. application.yml, 환경 변수, command line argument, config tree, profile-specific file 같은 다양한 소스가 하나의 Environment로 합쳐진다. 이 구조를 Externalized Configuration이라고 부른다.
설정은 단순 문자열이 아니다. DB 연결, 외부 API endpoint, timeout, feature flag, 보안 비밀값, pool 크기처럼 운영 안정성과 직결된다. 그래서 설정을 어떻게 묶고 검증하고 환경별로 나눌지가 중요하다.
@Value와 @ConfigurationProperties
@Value는 단일 값을 빠르게 가져올 때 편하다. 하지만 관련 설정이 여러 개라면 @ConfigurationProperties가 낫다. prefix 단위로 설정을 묶고, 타입 변환, relaxed binding, 검증, metadata 지원을 붙일 수 있기 때문이다.
@ConfigurationProperties(prefix = "payment.client")
public record PaymentClientProperties(
URI baseUrl,
Duration connectTimeout,
Duration readTimeout
) {
}@Configuration
@EnableConfigurationProperties(PaymentClientProperties.class)
public class PaymentClientConfig {
@Bean
PaymentClient paymentClient(PaymentClientProperties properties) {
return new PaymentClient(
properties.baseUrl(),
properties.connectTimeout(),
properties.readTimeout());
}
}이 방식은 설정이 하나의 의미 단위로 보인다. timeout 하나가 누락되었는지, URL 형식이 맞는지, 어떤 Bean이 설정을 사용하는지 추적하기 쉽다. @ConfigurationProperties 객체는 가능한 한 Environment 값의 구조를 표현하는 데 집중하고, 다른 Bean을 주입해 비즈니스 로직을 수행하는 객체로 만들지 않는 편이 좋다.
Profile
Profile은 환경별 설정과 Bean을 나누는 도구다. local, test, prod처럼 실행 환경에 따라 다른 datasource, mock client, logging level을 선택할 수 있다.
하지만 Profile은 남용하면 위험하다. Profile별로 Bean 구성이 크게 달라지면 local에서는 통과한 코드가 prod에서 다른 Bean graph로 실행될 수 있다. 기능 토글이나 고객별 분기를 Profile로 처리하면 배포 조합이 복잡해지고 테스트 매트릭스가 늘어난다.
좋은 Profile 사용은 환경 차이를 최소화한다. 비밀값은 외부 secret 관리에 두고, 운영 값은 환경 변수나 배포 설정으로 주입하며, 코드 안 Profile 조건은 꼭 필요한 경우로 제한한다.
설정 우선순위
Boot는 여러 설정 소스를 정해진 순서로 읽는다. 같은 key가 여러 곳에 있으면 더 높은 우선순위의 값이 이전 값을 덮어쓴다. 그래서 application.yml에 값이 있어도 command line argument나 환경 변수가 다른 값을 줄 수 있다.
문제가 생기면 “파일에 뭐가 적혀 있나”보다 “실제 Environment에 어떤 값이 들어갔나”를 확인해야 한다. Actuator env endpoint나 설정 바인딩 로그가 도움이 된다.
환경 변수는 운영체제 제약 때문에 점 표기 대신 대문자와 underscore 표기를 사용한다. 예를 들어 payment.client.base-url은 PAYMENT_CLIENT_BASE_URL처럼 들어올 수 있으며, Boot의 relaxed binding이 이를 설정 객체에 연결한다.
실전 팁
- 세 개 이상의 관련 설정은
@ConfigurationProperties로 묶는다. - 설정 객체에는 validation을 붙여 애플리케이션 시작 시 잘못된 설정을 빨리 실패시킨다.
Optional을 설정 필드 타입으로 남용하지 않는다. 값의 부재는 nullable, 기본값, validation 정책 중 무엇이 맞는지 명확히 정한다.- Profile별 Bean 차이는 최소화하고 값 차이는 외부 설정으로 주입한다.
- 비밀값은 Git에 두지 않는다.
- 운영 장애 조사 때는 실제 적용 값과 설정 소스 우선순위를 확인한다.
위험 신호!
@Value가 여러 클래스에 흩어져 같은 prefix 설정을 나눠 읽는다.@ConfigurationProperties클래스가 다른 Bean을 주입받아 설정 객체와 서비스 객체 역할을 동시에 한다.- Profile마다 완전히 다른 Bean graph가 만들어진다.
- 운영 비밀값이
application-prod.yml에 커밋되어 있다. - 설정 누락이 런타임 요청 중에야 드러난다.
- 설정 파일 값만 보고 실제 환경 변수 override를 확인하지 않는다.
확인 질문
확인 질문
@ConfigurationProperties의 장점은 무엇인가?
- 관련 설정을 타입 안전한 객체로 묶고 검증과 메타데이터 지원을 받을 수 있다는 점이다.
- Profile을 남용하면 어떤 문제가 생기는가?
- 환경별 Bean graph와 설정 조합이 달라져 실제 운영 구성을 추적하고 테스트하기 어려워진다.
- 설정 충돌 문제를 볼 때 파일 내용만 보면 안 되는 이유는 무엇인가?
- Boot에는 설정 소스 우선순위가 있어 환경 변수나 command line 값이 파일 값을 덮어쓸 수 있기 때문이다.