이전 포스팅에 이어 멀티모듈 구조 적용하기 2편을 시작하겠습니다!🫡
혹시 이전 포스팅을 못보신 분들은 이전글을 참고하세요👏
해당 포스팅은 application.yml에 대한 내용이 아닌 외부 환경 변수 파일을 추가적으로 설정하여, 프로퍼티를 적용하는 방법에 대한 내용입니다.💡
1. application.yml 로드 (Spring Boot Configuration Processing)
↓
2. Spring Boot Auto Configuration 실행
- DataSourceAutoConfiguration
- JpaAutoConfiguration
- LoggingAutoConfiguration
등등...
↓
3. Spring Context 생성 시작
↓
4. @Configuration 클래스들 처리
↓
5. @PropertySource 로드 ← 이미 너무 늦음!
위 흐름에 따르면 JPA와 DataSource 등의 설정은 @Configuration 클래스를 이용하여 처리하면 이미 구성이 완료된 상태로 수정이 되지 않기 때문에 아래 로드 방법 중 spring.profiles.config.import 방식을 사용하는 것이 가장 안전합니다.
# module-syste/resources/application.yml
spring:
profiles:
active: dev
config:
import:
- classpath:application-common.yml
- classpath:application-common-${spring.profiles.active}.yml
springboot에서 환경 변수 및 외부 설정 파일을 로드하는 방법은 다음과 같습니다.
정보를 상속받고 싶은 경우에 특정 모듈에 정의된.yml 경로를 지정하여 사용할 수 있으며, 추가되는 설정 파일 만큼 경로를 계속 지정해야 한다는 번거로움이 존재할 수 있고, 오타로 인해 예상치 못한 오류가 발생 할 수 있습니다.
spring:
config:
import:
- classpath:spring-config-import.yml
spring.profiles.active 또는 spring.profiles.include되는 프로파일에 따라 설정 파일을 로드하는 원리를 이용하여 특정 프로파일을 명시하여 포함되도록 하는 방식입니다.
spring:
profiles:
include: spring-profiles-include
주의사항은 include에 작성된 프로파일은 application-프로파일명.yml로 작성된 파일의 프로파일명에 해당합니다. 이러한 특성으로 네이밍 컨벤션에 대한 숙지가 정확하지 않으면 의도한대로 적용되기가 쉽지 않습니다.
외부 설정 파일의 내용을 로드하여 애플리케이션의 다양한 환경 설정 값에 접근할 수 있도록 돕는 어노테이션으로 @Configuration이 적용된 클래스에 주로 사용됩니다.
@Configuration
@Profile("!dev && !prod") // dev와 prod가 아닌 모든 프로파일(default)
@PropertySource(value = "classpath:/external-config-common.yml")
public class DefaultConfig {}
@Profile 어노테이션과 함께 사용하여 프로파일에 맞는 설정 파일 적용도 가능합니다.
저는 설정 파일의 추가 등을 고려하여 확장성을 보장하고, 코드 기반으로 설정을 하는 것이 휴먼 에러를 방지할 수 있다고 판단했기 때문에
@PropertySource와 @Profile을 적용하여 환경 변수 파일을 구성하겠습니다.
환경 변수 파일 적용
springboot에서 환경 변수를 읽는 순서는 다음과 같습니다.
환경 변수를 읽어들이는 순서를 고려하여 다음과 같은 시나리오로 환경 변수를 구성하겠습니다.
위 시나리오에서 핵심은 module-common에서 환경 변수 파일을 application-common.yml로 정의하여 기본 설정 파일의 우선 순위를 보장하고 Config 클래스를 통한 외부 환경 변수 파일 로드를 수행합니다. 이후 필요에 따라 특정 모듈에서 기본 설정 파일인 application.yml을 작성하여 오버라이드 하는 방식으로 동작을 처리합니다.
Environment객체가 생성되는 시점은 AbstractApplicationContext가 refresh()를 수행하면 내부에서 prepareRefresh()가 호출되는데 해당 위치에서 객체를 생성하고, 검증하며 prepareBeanFactory()메서드에서 생성된 객체를 빈으로 등록합니다.
Config 클래스 선언
@Configuration
@Profile("!dev && !prod")
@PropertySource(
value = "classpath:/external-config-common.yml",
factory = YamlPropertySourceFactory.class
)
public class DefaultConfig {}
@Configuration
@Profile("dev")
@PropertySource(
value = "classpath:/external-config-common-dev.yml",
factory = YamlPropertySourceFactory.class
)
class DevConfig {}
@Configuration
@Profile("prod")
@PropertySource(
value = "classpath:/external-config-prod.yml",
factory = YamlPropertySourceFactory.class
)
class ProductionConfig {}
dev, prod, default프로파일에 대한 Config 클래스들을 정의합니다.
Factory class
public class YamlPropertySourceFactory implements PropertySourceFactory {
@Override
public PropertySource<?> createPropertySource (String name, EncodedResource resource) {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(resource.getResource().getFilename(), properties);
}
}
경로는 module-common/src/main/resources/ 하위에 작성 합니다.
-project-root
|-build
|-module-app
|-module-common
|-src
|-main
|-java
|-resources
|-application-common.yml # default
|-application-common-dev.yml # 개발환경
|-application-common-prod.yml # 운영환경
|-external-config-common.yml
|-external-config-common-dev.yml
|-...
|-...기타모듈
이렇게 작성하면 Config 클래스에서 파일을 로드할 준비가 완료됩니다.
-project-root
|-build
|-module-app
|-module-common
|-module-customer
|-src
|-main
|-java
|-resources
|-application.yml # default
|-application-dev.yml # 개발환경
|-application-prod.yml # 운영환경
|-...기타모듈
springboot에서 인식 가능한 기본 설정 파일 포맷으로 환경 변수 파일을 작성하면 module-common 이 먼저 로드되고나서 해당 모듈의 springboot가 기본 설정 파일의 정보를 읽고, 설정 정보를 덮어 씌웁니다.
module-customer에서 아래과 같이 application.yml을 정의하고, 같은 경로에 application-dev.yml을 작성한다면 module-common에서 정의했던 환경 변수가 module-customer에서 정의한 dev프로파일의 환경 변수 파일로 덮어씌워집니다.
spring:
profiles:
active: dev
config:
import:
- classpath:application-common.yml
- classpath:application-common-${spring.profiles.active}.yml
여기까지 완료를 하셨다면 @SpringBootApplication 어노테이션이 명시된 클래스의 main 메서드를 실행했을 경우 평소와 같이 정상적으로 애플리케이션이 실행되어야 합니다.
또한, 마지막 예시에서 active를 dev로 설정한 결과가 로그에 아래와 같이 출력되면 됩니다!👏
: The following 1 profiles are active: "dev"