문제 : Multi Module yml 파일 분리 안됨
문제 상황
멀티 모듈 프로젝트 환경에서 yml 파일을 각 모듈의 역할에 맞게 관리하고자 했다.
기존에는 yml 파일 내용이 중복으로 저장되고 있어, 이를 수정하거나 삭제할 때 번거로움이 있었다.
이를 A 모듈은 A와 관련된 설정 파일만 관리하고 B 모듈은 B와 관련된 설정 파일만 관리하도록 변경했다.
기대 효과
- 중복되는 yml 설정 코드를 제거할 수 있다.
- 수정, 삭제 시 한 곳에서만 처리 가능하다.
- 각 모듈의 역할에 따라 설정 코드를 관리할 수 있다.
- 기존에는 실행 모듈의 yml에 모든 정보를 가지고 있어, 어느 모듈에 대한 설정 코드인지 알기 어려움
Multi Module 분리하기
A, B, C 모듈이 있다고 했을 때 각 모듈이 담당하는 설정들을 모듈 내 yml 파일에 관리하도록 분리했다.
그리고, 실행 모듈 yml에 spring.config.import 문에서 서브 모듈의 yml 위치를 지정해서 실행해 보았다.
# A는 B, C 모듈 의존
A -> B
-> C
# B는 C 모듈 의존
B -> C
# C는 Only Module
C
# B Module/src/main/resources/application-common.yml
common:
setting: "This is a common setting"
# A Module/src/main/resources/application.yml (실행 모듈)
spring:
config:
import: "classpath:application-common.yml"
로컬 환경에서는 루트 프로젝트 내에 모듈이 같이 존재하고 있어서, 정상적으로 실행됐다.
❌ build test : fail
하지만,
개발 및 운영 환경에서도 운영되는지 build 테스트를 하는데 sub-module의 yml 파일을 찾을 수 없다는 에러가 발생했다.
./gradlew clean build
spring.config.import 에 지정한 yml을 읽을 수 있어야 하는데, 찾을 수 없다는 에러가 나와 spring.config.import에서 어떻게 yml 파일을 인식하는지 찾아봤다.
spring.config.import
yml 파일에서 서브 모듈의 리소스 파일을 classpath: 경로로 지정할 수 있다. 이는 현재 실행되는 애플리케이션의 classpath 내에서만 탐색이 이루어지는 방식이다.
(다른 서브 모듈의 리소스 파일을 직접 지정할 수 없어 "classpath:application-common.yml" 처럼 고유하게 작성해 주어야 한다.)
Why 로컬에서는 실행되는데, 빌드 시에는 실행이 안되는지
1. 로컬 환경
- Gradle과 IDE는 sub-module/src/main/resources를 classpath에 포함시켜 줘서 실행 시 서브 모듈의 리소스 파일을 인식할 수 있다.
2. JAR
- sub-module이 별도로 JAR 패키징 되지 않으면, classpath 경로로 인식할 수 없다.
sub module yml이 build 폴더에 없어요..
A 모듈을 실행 모듈이라 하고, A 모듈을 빌드한 결과를 봤을 때 build/resources/main 내에 A 모듈의 yml 만 있고 서브 모듈의 yml 파일은 같이 말리지 않았다.
💬 sub-moudle의 리소스 파일이 포함된 상태로 build 되어야, 올바르게 JAR 파일이 만들어짐을 알 수 있었다.
Sub Module의 yml 파일을 빌드할 때 추가하고, 실행 중에 올바르게 인식하기 위해서 2가지 방법이 떠올랐다.
- Spring Config Server를 구축해서 yml 파일을 전달받는다. ❌
- 하지만 위 방법을 실행하기 위해서는 하나의 서버를 더 개발해야 되고, 관리해야 하는 포인트가 늘어나는 문제가 있다. 또한, 현재 프로젝트 구조는 모놀리식 아키텍처여서 효율적인 방안이라고 생각이 들지 않았다.
- MSA 환경에서는 Config Server를 사용하거나, 또는 Kubernetes ConfigMap을 사용해서 처리하기도 하는 것 같다.
- Build 시 Sub Module yml 파일 추가하기 ⭕️
- 위 방법이 가장 간단하고 빠르게 적용시킬 수 있는 방법이라 생각이 들었다.
- 기존에는 Gradle이 모든 의존성을 포함해 하나의 JAR 파일을 만드는데, 여기에 sub module의 yml을 복사하여 JAR 파일이 생성되도록 했다.
Multi Module JAR file 만들어지는 과정
상황
A 모듈이 B 모듈을 의존성으로 추가하고 빌드하는 경우, B 모듈의 컴파일된 결과물이 A 모듈에 포함(참조) 된다.
멀티 모듈 환경에서 빌드 시 포함되는 항목
- 컴파일된 클래스 파일 : B 모듈에서 컴파일된 .class 파일들이 A 모듈이 빌드될 때 포함된다. (A 모듈에서 B의 클래스들을 참조 가능)
포함 안되는 항목
- 리소스 파일 (yml, properties..) : 기본적으로 Sub Module의 리소스 파일은 JAR에 포함되지 않는다. resources 폴더 내의 파일은 각 모듈 빌드시 resources에 포함되지만, 다른 모듈의 리소스 파일은 포함되지 않는다. ‼️
결론
- A 모듈의 build.gradle에 B 모듈을 의존성으로 추가하면, 빌드 시 B 모듈의 컴파일된 JAR 파일이 A 모듈의 classpath에 포함된다. A 모듈에서 B 모듈의 클래스를 참조할 수 있음
- B 모듈의 Resource 파일은 A 빌드 출력되지 포함되지 않는다.
📌 Sub Module Yml 파일 빌드 출력하기
방법 1) task를 통한 yml 파일 copy 하기
A 모듈을 실행할 때 B 모듈 설정 파일을 포함하기 위해, build.gradle에 파일 복사 코드를 추가했다.
task copyBConfig(type: Copy) {
from project(':B').file('src/main/resources/application-b.yml')
into file("${buildDir}/resources/main")
}
processResources.dependsOn copyBConfig
${buildDir} → Deprecated 되어 다르게 처리해야 함 layout.buildDirectory.dir()을 통해 빌드 출력 상황에서 원하는 디렉터리를 찾을 수 있습니다.
task copyBConfig(type: Copy) {
from project(':B').file('src/main/resources/application-b.yml')
into layout.buildDirectory.dir("resources/main")
}
processResources.dependsOn copyBConfig
위 스크립트는 B의 yml 파일을 A 모듈의 resources/main 디렉터리에 복사해 주는 코드이다.
Gradle의 Copy 태스크를 활용해 B 모듈의 리소스가 A 모듈의 빌드 출력에 포함된다.
이를 통해 A 모듈은 classpath:b-module:application-b.yml 으로 접근할 수 있다.
processResources.dependsOn copyBConfig 무슨 코드인지?
processResoures는 Gradle에서 빌드할 때 사용하는 태스크 중 하나로, src/main/resources에 있는 파일들을 빌드 디렉토리로 복사하는 역할을 수행한다.
build 전체 실행 순서
- copyBConfig 실행 → application-b.yml이 build/resources/main에 복사됨
- processResources 실행 → src/main/resources에 있는 나머지 리소스들도 build/resources/main으로 복사됨
- 이후 jar 태스크가 실행되면서 build/resources/main에 있는 모든 리소스를 JAR 파일에 포함하여 빌드
./gradlew build --info

✅ 방법 2) 기존 task 작업 이후 yml 파일 copy 하기
processResources.doLast {
copy {
from project(':B').file('src/main/resources/application-b.yml')
into layout.buildDirectory.dir("resources/main")
}
}
- 위 방법으로도 서브 모듈의 리소스 파일을 빌드 출력할 수 있다.
- doLast { … } 는 별도 태스크 없이 processResources가 끝난 이후에 추가 작업을 설정할 수 있다.
- 기존 실행 모듈의 processResources를 실행하고 이후에 필요한 리소스 파일을 복사하는 순서로 처리했다.
./gradlew build 처리 과정
빌드 실행 과정은 아래와 같다.
processResources는 컴파일 처리가 아닌 리소스 파일을 복사해 주는 역할을 한다. 리소스 파일이 먼저 설정되어야, .class 파일 내 @Value 등 값을 초기화하여 bootJar 파일을 만들 수 있어 먼저 실행된다.
# 실행 모듈
Task :processResources # 리소스 복사
Task :classes # 리소스 + 컴파일된 클래스 포함
Task : bootJar # JAR 실행 파일 생성
Task: test Task # test 관련 테스크 실행
# 서브 모듈
Task :compileJava
Task :processResources # no-source
Task :classes
Task :jar # JAR 파일 생성
🔥 트러블 슈팅
processResources.dependsOn copyDbConfig 방법 1로 테스트 하면서 gradle에서 task 실행 순서를 학습했다.
- dependsOn으로 실행하게 되면 기존 processResources 가 실행된 이후에 실행된다.
- 빌드하는 모듈의 리소스 파일을 먼저 빌드 출력하고, 서브 모듈의 리소스 파일을 추가하고자 실행 순서를 아래로 바꿔 보았다.
- finalizedBy 으로 실행하게 되면 메인 모듈의 processResources가 실행된 후에 아무 때나 실행되도록 한다.
- 다른 Sub Module의 Resource 파일이 explict 하게 실행 순서가 명시되지 않아 에러가 발생했었다.
- mustRunAfter를 통해 실행하면, 메인 모듈의 processResource 실행 이후에 task가 실행됨을 의미하는데, 항상 실행됨을 보장되지 않는다.
- 아래와 같이 설정한 copyDbConfig가 실행되지 않았다.
'Spring Framework > Spring boot' 카테고리의 다른 글
Spring BeanFactoryPostProcessor로 글로벌 @Lazy 설정 및 Bean 초기화하기 (0) | 2025.01.21 |
---|---|
Spring Boot @Lazy를 사용한 Bean 지연 초기화하기 (0) | 2025.01.21 |
스프링부트 @SpringBootTest, @ContextConfiguration, @WebMvcTest, @AutoConfigureMockMvc 주요 테스트 어노테이션 정리 (0) | 2024.11.27 |
Spring Boot Open-In-View 설정과 데이터베이스 성능 최적화, 영속성 컨텍스트 활용법 (1) | 2024.10.26 |
[SpringBoot] Spring에서 @Transactional과 try-catch 사용 시 롤백되지 않는 이유 (0) | 2024.10.09 |
문제 : Multi Module yml 파일 분리 안됨
문제 상황
멀티 모듈 프로젝트 환경에서 yml 파일을 각 모듈의 역할에 맞게 관리하고자 했다.
기존에는 yml 파일 내용이 중복으로 저장되고 있어, 이를 수정하거나 삭제할 때 번거로움이 있었다.
이를 A 모듈은 A와 관련된 설정 파일만 관리하고 B 모듈은 B와 관련된 설정 파일만 관리하도록 변경했다.
기대 효과
- 중복되는 yml 설정 코드를 제거할 수 있다.
- 수정, 삭제 시 한 곳에서만 처리 가능하다.
- 각 모듈의 역할에 따라 설정 코드를 관리할 수 있다.
- 기존에는 실행 모듈의 yml에 모든 정보를 가지고 있어, 어느 모듈에 대한 설정 코드인지 알기 어려움
Multi Module 분리하기
A, B, C 모듈이 있다고 했을 때 각 모듈이 담당하는 설정들을 모듈 내 yml 파일에 관리하도록 분리했다.
그리고, 실행 모듈 yml에 spring.config.import 문에서 서브 모듈의 yml 위치를 지정해서 실행해 보았다.
# A는 B, C 모듈 의존
A -> B
-> C
# B는 C 모듈 의존
B -> C
# C는 Only Module
C
# B Module/src/main/resources/application-common.yml
common:
setting: "This is a common setting"
# A Module/src/main/resources/application.yml (실행 모듈)
spring:
config:
import: "classpath:application-common.yml"
로컬 환경에서는 루트 프로젝트 내에 모듈이 같이 존재하고 있어서, 정상적으로 실행됐다.
❌ build test : fail
하지만,
개발 및 운영 환경에서도 운영되는지 build 테스트를 하는데 sub-module의 yml 파일을 찾을 수 없다는 에러가 발생했다.
./gradlew clean build
spring.config.import 에 지정한 yml을 읽을 수 있어야 하는데, 찾을 수 없다는 에러가 나와 spring.config.import에서 어떻게 yml 파일을 인식하는지 찾아봤다.
spring.config.import
yml 파일에서 서브 모듈의 리소스 파일을 classpath: 경로로 지정할 수 있다. 이는 현재 실행되는 애플리케이션의 classpath 내에서만 탐색이 이루어지는 방식이다.
(다른 서브 모듈의 리소스 파일을 직접 지정할 수 없어 "classpath:application-common.yml" 처럼 고유하게 작성해 주어야 한다.)
Why 로컬에서는 실행되는데, 빌드 시에는 실행이 안되는지
1. 로컬 환경
- Gradle과 IDE는 sub-module/src/main/resources를 classpath에 포함시켜 줘서 실행 시 서브 모듈의 리소스 파일을 인식할 수 있다.
2. JAR
- sub-module이 별도로 JAR 패키징 되지 않으면, classpath 경로로 인식할 수 없다.
sub module yml이 build 폴더에 없어요..
A 모듈을 실행 모듈이라 하고, A 모듈을 빌드한 결과를 봤을 때 build/resources/main 내에 A 모듈의 yml 만 있고 서브 모듈의 yml 파일은 같이 말리지 않았다.
💬 sub-moudle의 리소스 파일이 포함된 상태로 build 되어야, 올바르게 JAR 파일이 만들어짐을 알 수 있었다.
Sub Module의 yml 파일을 빌드할 때 추가하고, 실행 중에 올바르게 인식하기 위해서 2가지 방법이 떠올랐다.
- Spring Config Server를 구축해서 yml 파일을 전달받는다. ❌
- 하지만 위 방법을 실행하기 위해서는 하나의 서버를 더 개발해야 되고, 관리해야 하는 포인트가 늘어나는 문제가 있다. 또한, 현재 프로젝트 구조는 모놀리식 아키텍처여서 효율적인 방안이라고 생각이 들지 않았다.
- MSA 환경에서는 Config Server를 사용하거나, 또는 Kubernetes ConfigMap을 사용해서 처리하기도 하는 것 같다.
- Build 시 Sub Module yml 파일 추가하기 ⭕️
- 위 방법이 가장 간단하고 빠르게 적용시킬 수 있는 방법이라 생각이 들었다.
- 기존에는 Gradle이 모든 의존성을 포함해 하나의 JAR 파일을 만드는데, 여기에 sub module의 yml을 복사하여 JAR 파일이 생성되도록 했다.
Multi Module JAR file 만들어지는 과정
상황
A 모듈이 B 모듈을 의존성으로 추가하고 빌드하는 경우, B 모듈의 컴파일된 결과물이 A 모듈에 포함(참조) 된다.
멀티 모듈 환경에서 빌드 시 포함되는 항목
- 컴파일된 클래스 파일 : B 모듈에서 컴파일된 .class 파일들이 A 모듈이 빌드될 때 포함된다. (A 모듈에서 B의 클래스들을 참조 가능)
포함 안되는 항목
- 리소스 파일 (yml, properties..) : 기본적으로 Sub Module의 리소스 파일은 JAR에 포함되지 않는다. resources 폴더 내의 파일은 각 모듈 빌드시 resources에 포함되지만, 다른 모듈의 리소스 파일은 포함되지 않는다. ‼️
결론
- A 모듈의 build.gradle에 B 모듈을 의존성으로 추가하면, 빌드 시 B 모듈의 컴파일된 JAR 파일이 A 모듈의 classpath에 포함된다. A 모듈에서 B 모듈의 클래스를 참조할 수 있음
- B 모듈의 Resource 파일은 A 빌드 출력되지 포함되지 않는다.
📌 Sub Module Yml 파일 빌드 출력하기
방법 1) task를 통한 yml 파일 copy 하기
A 모듈을 실행할 때 B 모듈 설정 파일을 포함하기 위해, build.gradle에 파일 복사 코드를 추가했다.
task copyBConfig(type: Copy) {
from project(':B').file('src/main/resources/application-b.yml')
into file("${buildDir}/resources/main")
}
processResources.dependsOn copyBConfig
${buildDir} → Deprecated 되어 다르게 처리해야 함 layout.buildDirectory.dir()을 통해 빌드 출력 상황에서 원하는 디렉터리를 찾을 수 있습니다.
task copyBConfig(type: Copy) {
from project(':B').file('src/main/resources/application-b.yml')
into layout.buildDirectory.dir("resources/main")
}
processResources.dependsOn copyBConfig
위 스크립트는 B의 yml 파일을 A 모듈의 resources/main 디렉터리에 복사해 주는 코드이다.
Gradle의 Copy 태스크를 활용해 B 모듈의 리소스가 A 모듈의 빌드 출력에 포함된다.
이를 통해 A 모듈은 classpath:b-module:application-b.yml 으로 접근할 수 있다.
processResources.dependsOn copyBConfig 무슨 코드인지?
processResoures는 Gradle에서 빌드할 때 사용하는 태스크 중 하나로, src/main/resources에 있는 파일들을 빌드 디렉토리로 복사하는 역할을 수행한다.
build 전체 실행 순서
- copyBConfig 실행 → application-b.yml이 build/resources/main에 복사됨
- processResources 실행 → src/main/resources에 있는 나머지 리소스들도 build/resources/main으로 복사됨
- 이후 jar 태스크가 실행되면서 build/resources/main에 있는 모든 리소스를 JAR 파일에 포함하여 빌드
./gradlew build --info

✅ 방법 2) 기존 task 작업 이후 yml 파일 copy 하기
processResources.doLast {
copy {
from project(':B').file('src/main/resources/application-b.yml')
into layout.buildDirectory.dir("resources/main")
}
}
- 위 방법으로도 서브 모듈의 리소스 파일을 빌드 출력할 수 있다.
- doLast { … } 는 별도 태스크 없이 processResources가 끝난 이후에 추가 작업을 설정할 수 있다.
- 기존 실행 모듈의 processResources를 실행하고 이후에 필요한 리소스 파일을 복사하는 순서로 처리했다.
./gradlew build 처리 과정
빌드 실행 과정은 아래와 같다.
processResources는 컴파일 처리가 아닌 리소스 파일을 복사해 주는 역할을 한다. 리소스 파일이 먼저 설정되어야, .class 파일 내 @Value 등 값을 초기화하여 bootJar 파일을 만들 수 있어 먼저 실행된다.
# 실행 모듈
Task :processResources # 리소스 복사
Task :classes # 리소스 + 컴파일된 클래스 포함
Task : bootJar # JAR 실행 파일 생성
Task: test Task # test 관련 테스크 실행
# 서브 모듈
Task :compileJava
Task :processResources # no-source
Task :classes
Task :jar # JAR 파일 생성
🔥 트러블 슈팅
processResources.dependsOn copyDbConfig 방법 1로 테스트 하면서 gradle에서 task 실행 순서를 학습했다.
- dependsOn으로 실행하게 되면 기존 processResources 가 실행된 이후에 실행된다.
- 빌드하는 모듈의 리소스 파일을 먼저 빌드 출력하고, 서브 모듈의 리소스 파일을 추가하고자 실행 순서를 아래로 바꿔 보았다.
- finalizedBy 으로 실행하게 되면 메인 모듈의 processResources가 실행된 후에 아무 때나 실행되도록 한다.
- 다른 Sub Module의 Resource 파일이 explict 하게 실행 순서가 명시되지 않아 에러가 발생했었다.
- mustRunAfter를 통해 실행하면, 메인 모듈의 processResource 실행 이후에 task가 실행됨을 의미하는데, 항상 실행됨을 보장되지 않는다.
- 아래와 같이 설정한 copyDbConfig가 실행되지 않았다.
'Spring Framework > Spring boot' 카테고리의 다른 글
Spring BeanFactoryPostProcessor로 글로벌 @Lazy 설정 및 Bean 초기화하기 (0) | 2025.01.21 |
---|---|
Spring Boot @Lazy를 사용한 Bean 지연 초기화하기 (0) | 2025.01.21 |
스프링부트 @SpringBootTest, @ContextConfiguration, @WebMvcTest, @AutoConfigureMockMvc 주요 테스트 어노테이션 정리 (0) | 2024.11.27 |
Spring Boot Open-In-View 설정과 데이터베이스 성능 최적화, 영속성 컨텍스트 활용법 (1) | 2024.10.26 |
[SpringBoot] Spring에서 @Transactional과 try-catch 사용 시 롤백되지 않는 이유 (0) | 2024.10.09 |