Multi Project Build 구성하기StructureIdentifying project structureNaming recommendationSubproject 들 사이에 의존성 선언하기Multi-Project standardsManaging Common ConfigurationSpringBootApplication포함하는 모듈에서 다른 모듈의 빈 등록하는 방법다른 프로젝트의 test source 이용하기TroubleShooting여러 모듈로 나누면서 test 에서 발생하는 문제
Multi Project Build 구성하기
[ Gradle ] Creating a Basic Multi-Project Build
[ Github ] Multi project build 참고 (우테코)
[ Reflectoring.io ] Spring Boot Gradle Multi Module
[ Spring ] Creating a multi module project
Structure
Project layout . ├── app │ ... │ └── build.gradle └── settings.gradle // settings.gradle rootProject.name = 'basic-multiproject' include 'app' Project layout . ├── app │ ... │ └── build.gradle ├── lib │ ... │ └── build.gradle └── settings.gradle // settings.gradle rootProject.name = 'basic-multiproject' include 'app' include 'lib'
- Gradle에서 Multi project build는 root project(폴더 하나를 지칭하지 않고, 그냥 전체를 소스 코드를 포함하는 프로젝트라 생각하면 됨 )와 1개 이상의 subproject로 구성됨
- 위의 Project layout은
app
이라는 하나의subproject
를 갖고 있는 multi-project build 구성
Identifying project structure
[ Gradle ] Multi-Project path
> gradle -q projects ------------------------------------------------------------ Root project 'multiproject' ------------------------------------------------------------ Root project 'multiproject' +--- Project ':api' +--- Project ':services' | +--- Project ':services:shared' | \--- Project ':services:webservice' \--- Project ':shared' To see a list of the tasks of a project, run gradle <project-path>:tasks For example, try running gradle :api:tasks
Multi-Project path
- colon 이 없을 때 root project를 말함
- 다른 project들의 경로는 colon 기준으로 연속적으로 이어지는 project 이름으로 구성됨
Naming recommendation
- Subproject 의 이름은 folder 이름과 같게 유지하기. folder 이름과 다르게 custom 하게 지정할 수 있지만 굳이 그럴필요 없음
- project name 의 formatting은 kebab case로 이용 (e.g. kebab-case-formatting)
- settings.file에 root project name 정의하기
Subproject 들 사이에 의존성 선언하기
Multi-Project standards
Gradle community 는 multi-project build structure에 크게 2개의 표준을 갖고 있음
- Multi-Project Builds using buildSrc
- Composite Builds
<Deprecated>BuildSrc 이용하여 MultiProject Build├── buildSrc │ ... ├── api │ ├── src │ │ └──... │ └── build.gradle ├── services │ └── person-service │ ├── src │ │ └──... │ └── build.gradle ├── shared │ ├── src │ │ └──... │ └── build.gradle └── settings.gradle // settings.gradle rootProject.name = 'dependencies-java' include 'api', 'shared', 'services:person-service' // project path가 services 폴더 안에 있을때 : 으로 구분자를 사용
Managing Common Configuration
- 모든 subproject에서 쓰이는 공통 Configuration들 (repositories and dependencies .. )들을 project의 root 경로의 build.gradle에서 관리할 수 있음
allprojects { repositories { mavenCentral() } } subprojects { apply plugin: 'java' dependencies { testImplementation 'junit:junit:4.13.1' } }
SpringBootApplication포함하는 모듈에서 다른 모듈의 빈 등록하는 방법
- 애플리케이션 모듈에서 다른 모듈의 클래스를 직접 import 해서 Configuration에 빈으로 등록( 이 방식은 애플리케이션 모듈 → 다른 모듈로 의존성 발생하게 됨)
- Spring Multi Module Getting Started 에서 보면 아래와 같이 package를 애플리케이션 모듈과 같게 만들면 다른 모듈의
Bean
들이 자동으로@ComponentScan
에 의해 포함된다고 함 (그건 맞으나, implementation project로 의존성을 걸어준 프로젝트에 대해서만 찾을 수 있음)
Do not use the same package as the library (or a parent of the library package) unless you want to include all Spring components in the library by@ComponentScan
in the application. @SpringBootApplication(scanBasePackages = "com.example.multimodule") •@ComponentScan
: Tells Spring to look for other components, configurations, and services in thecom/example
package, letting it find the controllers.
다른 프로젝트의 test source 이용하기
[Gradle ] Using test fixtures
TroubleShooting
Execution failed for task ':common:bootJar'. > Error while evaluating property 'mainClass' of task ':common:bootJar'. > Failed to calculate the value of task ':common:bootJar' property 'mainClass'. > Main class name has not been configured and it could not be resolved
- bootJar 태스크를 실행할 때 위와 같은 에러가 발생함. bootJar 태스크 실행 위해서는 mainClass 프로퍼티가 필요한데, 이는 @SpringBootApplication 클래스를 담고 있는 모듈에서만 실행되면 됨
- 따라서, 해당 모듈에서는 bootJar를 스킵하면됨
tasks.named('bootJar') { enabled = false }
여러 모듈로 나누면서 test 에서 발생하는 문제
- fixture 클래스들 공통적으로 참조하고 싶다.
- @SpringBootApplication 을 2번 쓸수가 없어서, 해당 어노테이션을 사용하지 못하면서 bean, component 를 ApplicationContext에 올리기 위해서 수동으로 Annotation을 많이 붙여줘야 함 ⇒ main source set에서만 2번 안나오면 됨. test source set에 @SpringBootApplication 붙인 클래스를 만들어주면 됨
- Test context를 Project 별로 공유할 수 있지 않을까? StackOverflow — Reusing a spring test context across multiple Gradle projects → Gradle’s official documentation said
- TestContext가 static cache로 저장되고, test가 별도의 process 로 실행되면 해당 cache가 클린됨 → 재사용이 불가능. 따라서 test context cache 효과를 누리려면 모든 테스트는 하나의 프로세스 혹은 하나의 suite에서 동시에 다 진행되어야 한다.
Test are always run in (one or more) separate JVMs. The sample below shows various configuration options.
Project별로 별도의 JVM을 쓰기 때문에 Spring context또한 공유 불가능함
The Spring TestContext framework stores application contexts in a static cache. This means that the context is literally stored in a static variable. In other words, if tests execute in separate processes the static cache will be cleared between each test execution, and this will effectively disable the caching mechanism.To benefit from the caching mechanism, all tests must run within the same process or test suite. This can be achieved by executing all tests as a group within an IDE. Similarly, when executing tests with a build framework such as Ant, Maven, or Gradle it is important to make sure that the build framework does not fork between tests. For example, if the forkMode for the Maven Surefire plug-in is set to always or pertest, the TestContext framework will not be able to cache application contexts between test classes and the build process will run significantly slower as a result.
결론. 통합테스트를 하나의 컴포넌트로 몰고(@SpringBootApplication 이 있는 모듈). 나머지 단위테스트만 다른 컴포넌트에서 진행.