이제 진짜 멀티 모듈 프로젝트를 만들어 보자.
나 같은 경우에는 프로젝트를 처음 빌드할 때 일단 Spring Initializr 를 통해 기본 프로젝트 프레임을 잡고 시작한다.
gradle 기반으로 언어는 kotlin, 자바 버전은 17을 선택했다.
그동안은 자바 11을 주로 사용했지만, 17도 LTS 버전이니까 슬슬 넘어가는 게 좋지 않나 싶어서 골라봤다.

Dependencies 는 개발하면서 필요한 것들을 추가할 생각이므로 일단 적당히 골라준다.
크게 3개의 파일을 주로 수정하게 될 예정이다.

build.gradle.kts : build.gradle 의 kotlin dsl 버전이라고 보면 된다. 의존성 및 gradle 관련 설정을 작성한다.gradle.properties : gradle 관련 property 들을 작성한다. 주로 버전 정보 등이다.settings.gradle.kts : settings.gradle 의 kotlin dsl 버전이다. 프로젝트 관련 정보가 주로 작성된다.기본적인 프레임을 기반으로 멀티 모듈 구조로 하나씩 변경해볼 생각이다.
최종적으로 아래와 같은 느낌으로 구조가 바뀔 것이다.

일반적으로 멀티 모듈 프로젝트에서는 가장 밖에 있는 root project 에는 코드를 작성하지 않는다.
따라서 기본적으로 생성된 src 폴더를 과감하게 삭제하자.
이후 생성된 프로젝트(root project)의 build.gradle.kts 파일을 아래와 같이 수정한다.
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.5"
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"
}
java.sourceCompatibility = JavaVersion.VERSION_17
allprojects { // (1)
group = "com.tire"
version = "0.0.1-SNAPSHOT"
repositories {
mavenCentral()
}
}
subprojects { // (2)
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-validation")
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")
compileOnly("org.projectlombok:lombok")
runtimeOnly("com.h2database:h2")
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
annotationProcessor("org.projectlombok:lombok")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
}
allprojects 라는 건 root 프로젝트와 그 하위 프로젝트 모두에 적용되는 설정을 말한다. 사실 root 프로젝트에는 아무 것도 작성하지 않을 예정이므로 굳이 이곳에 무언가를 작성할 필요는 없다. 하지만, 나는 이곳에 group 및 version 정보 등을 작성했다.subprojects 는 이와 달리 root 프로젝트가 아닌 하위 프로젝트(모듈)들에 적용되는 설정을 말한다. 이곳에 작성한 것들은 하위 프로젝트 전체에 적용된다. "Sprint Initialize"가 만들어준 의존성 관련 코드들을 일단 모두 이곳으로 옮긴다.이렇게 한 뒤 gradle 을 빌드했을 때 정상적으로 빌드되는 걸 확인한다.
이제는 그럼 모듈을 하나씩 추가해보자.
최상단 프로젝트 폴더에서 모듈을 추가한다. (New - Module)

그리고 아래와 같이 New Module 을 선택해 모듈을 추가해준다.

개인적으로 여기서 Spring Initializr 를 선택해 삽질을 많이 했는데, 그냥 New Module 을 선택한 뒤 직접 설정하는 게 더 편한 것 같다.
tire-common: build.gradle.kts
import org.springframework.boot.gradle.tasks.bundling.BootJar
dependencies { // (1)
}
// (2)
val jar: Jar by tasks
val bootJar: BootJar by tasks
bootJar.enabled = false
jar.enabled = true
bootJar 를 enabled=false 로 설정해준다. 다만 빌드 및 배포는 가능해야 하므로, jar 는 enabled=true 로 설정한다.다음은 tire-domain 모듈이다.
tire-domain: build.gradle.kts
import org.springframework.boot.gradle.tasks.bundling.BootJar
plugins {
kotlin("plugin.jpa") // (1)
}
dependencies { // (2)
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
runtimeOnly("com.h2database:h2")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
val jar: Jar by tasks
val bootJar: BootJar by tasks
bootJar.enabled = false
jar.enabled = true
tire-domain 모듈에서 사용할 spring data jpa 관련 dependencies 를 작성한다. (root 프로젝트에 작성해 둔 dependencies 들을 복사해서 들고오면 편하다.)여기서 주의할 점은, 이 모듈을 의존하는 다른 모듈에서도 data jpa 의존성을 사용하고 싶은 경우
implementation이 아닌api를 사용해야 한다는 점이다. 나는 다른 모듈에서도 의존성이 필요한 경우, 해당 모듈에서도 필요한 의존성을implementation하는 게 적절하다고 생각되어implementation을 선택했다.
tire-erp: build.gradle.kts
dependencies {
implementation(project(":tire-common")) // (1)
implementation(project(":tire-domain"))
// (2)
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-security")
testImplementation("org.springframework.security:spring-security-test")
}
tire-erp 모듈은 tire-common 및 tire-domain 모듈을 의존한다. 따라서 해당 의존성을 작성해준다.tire-erp 모듈에서 필요한 의존성을 작성한다.tire-client 모듈도 tire-erp 모듈과 동일하게 작성한다.
이제 각 모듈에 의존성 작성을 했으니 루트 프로젝트의 의존성 파일을 손볼 필요가 있다.
루트 프로젝트에는 모든 모듈에서 공통적으로 사용할 kotlin 관련 의존성만을 작성한다.
root-project: build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.7.5" apply false // (1)
id("io.spring.dependency-management") version "1.0.15.RELEASE" apply false
kotlin("jvm") // (2)
kotlin("plugin.spring") apply false
kotlin("plugin.jpa") apply false
}
java.sourceCompatibility = JavaVersion.VERSION_17
allprojects {
group = "com.example"
version = "0.0.1-SNAPSHOT"
repositories {
mavenCentral()
}
}
apply false 를 작성한다.gradle.properties 파일 및 settings.gradle.kts 파일에 별도로 작성해 공통으로 사용한다.subprojects {
apply { // (3)
plugin("kotlin")
plugin("org.springframework.boot")
plugin("io.spring.dependency-management")
plugin("org.jetbrains.kotlin.plugin.spring")
}
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
dependencies { // (4)
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "17"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
}
root-project: gradle.properties
kotlin.code.style=official
kotlinVersion=1.7.20
root-project:settings.gradle.kts
pluginManagement { // (1)
val kotlinVersion: String by settings
plugins {
kotlin("jvm") version kotlinVersion
kotlin("plugin.spring") version kotlinVersion
kotlin("plugin.jpa") version kotlinVersion
}
}
rootProject.name = "tire-service"
include("tire-domain") // (2)
include("tire-erp")
include("tire-common")
include("tire-client")
include 처리해야 root project 에서 한 번에 빌드할 수 있다. intelliJ 를 통해 모듈을 추가했다면 자동으로 작성되어 있을 것이다. 혹시 누락되어 있다면 작성해주자.이제 gradle 을 빌드하면 아래와 같이 의존성이 잘 설정된 것을 확인할 수 있다.

api 가 아닌 implementation 을 사용했기 때문)오예! 이제 개발을 시작할 기본적인 준비가 완료됐다.
이제부터는 즐거운 개발 시간이다.
관련 전체 코드는 Github 에서 확인할 수 있다.