처음 프로젝트를 만들때 하나의 모듈로 모든 기능을 구현했다. 기능에 따라 도메인에 따라 패키지를 나누기도 하여 분리를 했다고 생각했지만, 또 다른 서버를 만들기 위해 새로운 프로젝트를 만들고 하는 일이 잦았다. 그리고 공통으로 사용해야하는 모듈이 생겼을때 또 모듈용 프로젝트를 새로 만들고 각각 관리했다.
프로젝트가 점점 커지면서 패키지의 수가 많아지고, 의존성이 뒤죽박죽 섞이기 시작했다. 공용으로 사용해야하는 모델들의 경우도 다른 개발자에게 변경사항을 알려주어야했다. 이러한 상황에서 좀더 유연하게 관리하기 위해 멀티모듈을 도입하려고 했지만, 설정하는 방법을 몰랐고 여기저기 찾아보면서 삽질을 시작했다.
사용 스펙
- IntelliJ IDEA 2022.3.1 (Ultimate Edition)
- Kotlin
- Gradle
- Spring Boot 및 기타 모듈들
Git
https://github.com/lds2292/kotlin-multi-module
프로젝트 만들기
당연하지만 처음은 프로젝트를 만드는 일부터 시작한다
Spring Initializr를 사용해도 되고 New Project를 선택하여 만들어도 된다.
여기서는 기본으로 만들어주는 build.gradle.kts를 위해 Spring Initializr를 사용했다
Language : Kotlin
Type : Gradle - Kotlin
JDK : 11
Java: 11
Packaging : Jar
모듈 폴더 설정하기
이제 프로젝트 하위에 모듈들을 설정한다
생성할 하위 모듈들
- api
- domain
- core
- infrastructure
최상단 프로젝트를 클릭한후 폴더를 만들어 준다
settings.gradle.kts 를 열어 아래와 같이 입력해준다.
include(
"example-api",
"example-core",
"example-domain",
"example-infrastructure",
)
그리고 귀여운 코끼리를 클릭하거나 Load Gradle Changes를 해준다
이후 Project를 보면 다음과같이 서브모듈로 설정된것을 볼 수 있다.
최상위 build.gradle.kts 설정하기
root에 있는 build.gradle.kts를 설정해보자. 솔직히 여기까지 완료한다면 멀티모듈 설정은 거의 끝이라고 보면된다. 여기 부분이 제일 어려웠던거 같다.
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.7"
id("io.spring.dependency-management") version "1.0.15.RELEASE"
kotlin("jvm") version "1.6.21"
kotlin("plugin.spring") version "1.6.21"
kotlin("plugin.jpa") version "1.6.21"
}
group = "com.browngoo"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
기본으로 만들어진 이 설정을 아래와 같이 변경해보자
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.7" apply false
id("io.spring.dependency-management") version "1.0.15.RELEASE" apply false
kotlin("jvm") version "1.6.21"
kotlin("plugin.spring") version "1.6.21" apply false
kotlin("plugin.jpa") version "1.6.21" apply false
}
allprojects{
group = "com.example"
version = "0.0.1-SNAPSHOT"
repositories {
mavenCentral()
}
}
java.sourceCompatibility = JavaVersion.VERSION_11
subprojects {
apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")
apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "org.jetbrains.kotlin.plugin.spring")
apply(plugin = "org.jetbrains.kotlin.plugin.jpa")
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
}
allProjects 안에 있는 내용은 최상위 포함 모든 프로젝트에 반영할 내용을 담고 있다
subProjects 안에 있는 내용은 하위 모듈에 적용할 내용을 담고 있다.
apply는 하위 프로젝트에 적용할 플러그인을 뜻한다
dependencies는 하위 프로젝트에 사용할 의존성들을 말한다.
이렇게 설정한후에 귀여운 코끼리를 클릭해주면 설정한 하위 모듈들에 dependencies의 의존성들이 전부 들어가게 된다.
최상위의 build.gradle.kts에는 공통으로 사용할 의존성들을 넣어두어 전역으로 관리하면 좋다. 각 모듈별로 필요한 의존성이 다르기 때문에 각 모듈에 맞게 의존성을 변경해보자.
그전에 최상위에 있는 src는 사용하지 않으니 삭제해도 된다.
하위 모듈별 의존성 설정
하위 모듈들이 전부 spring관련 설정을 의존하고 있다. 전부 의존할 필요가 없으니 하나씩 정리해 보자
먼저 최상위의 build.gradle.kts의 dependencies항목을 정리하자
기존
subprojects {
...
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
...
}
변경
subprojects {
...
dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
...
}
spring-web 이나 spring-data는 하위 모듈 전부는 필요가 없다. 공통으로 사용할 부분만 남겨놓자
하위 모듈 의존성 적용
이제 spring-web이 필요한 모듈에 의존성 설정을 해보자. example-api 폴더 밑에 build.gradle.kts를 만든다.
이제 example-api 밑에 있는 build.gradle.kts에 다음과 같이 의존성을 추가한다
그리고 귀여운 코끼리를 눌러주자. 그리고나서 의존성을 체크해 보면 example-api에만 spring-web이 적용된것이 보인다.
이렇게 각 하위 모듈에 build.gradle.kts를 설정하여 모듈마다 의존성을 다르게 설정할 수 있다.
모듈간 의존성 추가
example-api에 example-domain 모듈이 필요하다면 다음과 같이 build.gradle.kts에 설정하면 된다.
dependencies{
implementation(project(":example-domain"))
implementation("org.springframework.boot:spring-boot-starter-web")
}
귀여운 코끼리와 함께한다면 다음과 같이 의존성에 example-domain이 추가된것을 볼 수 있다.
이제 example-domain에 있는 클래스들을 사용할 수 있다.
기본 뼈대 만들기
example-api에는 build.gradle.kts만 만들어 뒀다. 이제 코드를 작성하기 위해 다음과 같이 폴더를 만들어주면된다. IntelliJ IDEA 사용시 자동으로 만들어 주는 기능도 있다.
디렉토리를 만들면 다음과 같이 나타난다
이제 우리가 보던 기본 뼈대가 만들어 졌다. 패키지를 만들고 Spring Boot를 실행해 보자.
package com.browngoo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class ExampleApiApplication
fun main(args: Array<String>) {
runApplication<ExampleApiApplication>(*args)
}
잘 실행되는것을 볼 수 있다.
생각해봐야할 사항
1. 모듈별로 분리했기 때문에 SpringBoot의 스캔대상 지정을 잘 해주어야한다. example-domain쪽에 Entity가 있다면 다음 어노테이션이 필요하다
@EntityScan(basePackages = ["com.browngoo"])
2. BootJar와 Jar를 구분하여 빌드되게 해야한다. 실제로 실행되는 Jar는 example-api다. 다른 모듈들은 Jar의 형태로만 있어도 된다. 각모듈에 다음과 같이 지정해주거나 최상위에서 지정해준다.
최상위 build.gradle.kts
subprojects {
...
tasks.getByName("bootJar"){
enabled = false
}
tasks.getByName("jar"){
enabled = true
}
dependencies {
...
}
...
}
하위모듈 build.gradle.kts - BootJar가 필요한 모듈
dependencies{
implementation(project(":example-domain"))
implementation("org.springframework.boot:spring-boot-starter-web")
}
tasks.getByName("bootJar"){
enabled = true
}
이렇게 하면 example-api는 BootJar 기타 다른모듈은 Jar로 만들어진다.
> Task :example-domain:bootJar SKIPPED
> Task :example-domain:jar UP-TO-DATE
> Task :example-api:bootJar
> Task :example-api:jar UP-TO-DATE
'개발 > Kotlin' 카테고리의 다른 글
Spring REST Docs 사용하기 - 테스트코드 작성 (0) | 2023.03.18 |
---|---|
Spring REST Docs 사용하기 - 설정 (0) | 2023.03.15 |
Kotlin - Enum 사용 (0) | 2022.07.22 |
Kotlin DSL로 Gradle 버전관리하기 (0) | 2022.07.21 |
TransactionalEventListener 그리고 EventListener (0) | 2022.07.12 |