[개인 프로젝트] 타이어 서비스(2) - 멀티 모듈 빌드

이영규·2022년 11월 6일

tire-service

목록 보기
3/3

1. 기본 프로젝트 빌드

이제 진짜 멀티 모듈 프로젝트를 만들어 보자.

나 같은 경우에는 프로젝트를 처음 빌드할 때 일단 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 버전이다. 프로젝트 관련 정보가 주로 작성된다.

2. 루트 프로젝트 수정

기본적인 프레임을 기반으로 멀티 모듈 구조로 하나씩 변경해볼 생각이다.

최종적으로 아래와 같은 느낌으로 구조가 바뀔 것이다.

일반적으로 멀티 모듈 프로젝트에서는 가장 밖에 있는 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()
    }
}
  • (1) : allprojects 라는 건 root 프로젝트와 그 하위 프로젝트 모두에 적용되는 설정을 말한다. 사실 root 프로젝트에는 아무 것도 작성하지 않을 예정이므로 굳이 이곳에 무언가를 작성할 필요는 없다. 하지만, 나는 이곳에 group 및 version 정보 등을 작성했다.
  • (2) : subprojects 는 이와 달리 root 프로젝트가 아닌 하위 프로젝트(모듈)들에 적용되는 설정을 말한다. 이곳에 작성한 것들은 하위 프로젝트 전체에 적용된다. "Sprint Initialize"가 만들어준 의존성 관련 코드들을 일단 모두 이곳으로 옮긴다.

이렇게 한 뒤 gradle 을 빌드했을 때 정상적으로 빌드되는 걸 확인한다.


3. 모듈 생성

이제는 그럼 모듈을 하나씩 추가해보자.

최상단 프로젝트 폴더에서 모듈을 추가한다. (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
  • (1) : common 모듈은 아무런 의존성도 사용하지 않을 생각이다. kotlin 관련 기본적인 의존성이 필요하긴 하지만, 이건 root project 에 작성해 전달받을 생각이다.
  • (2) : common 모듈은 독립적으로 실행가능한 서비스가 아니다. 따라서 bootJarenabled=false 로 설정해준다. 다만 빌드 및 배포는 가능해야 하므로, jarenabled=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
  • (1) : spring jpa 를 사용할 예정이므로 jpa 관련 플러그인을 적용해 준다. 그외 다른 플러그인은 root project 에서 공통으로 설정할 예정이다. 플러그인 버전은 root project 에서 작성한 버전을 따라간다.
  • (1) : 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")
}
  • (1) : tire-erp 모듈은 tire-commontire-domain 모듈을 의존한다. 따라서 해당 의존성을 작성해준다.
  • (2) : 이후 그외 tire-erp 모듈에서 필요한 의존성을 작성한다.

tire-client 모듈도 tire-erp 모듈과 동일하게 작성한다.


4. 루트 프로젝트 최종 수정

이제 각 모듈에 의존성 작성을 했으니 루트 프로젝트의 의존성 파일을 손볼 필요가 있다.
루트 프로젝트에는 모든 모듈에서 공통적으로 사용할 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()
    }
}
  • (1) : root project 에는 필요하지 않은 플러그인이므로, apply false 를 작성한다.
  • (2) : jvm 플러그인은 적용한다. kotlin 관련 버전 정보는 여러 플러그인에 공통으로 사용되므로 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()
    }
}
  • (3) : 위에서 plugin 을 apply false 처리 했으므로 subprojects 부분에서 plugin 을 다시 apply 해준다. jpa 플러그인은 도메인 모듈에서만 사용하므로 해당 모듈에서 별도로 적용한다.
  • (4) : 각 모듈에서 필요한 의존성을 모두 가져갔고, 공통적으로 적용할 kotlin 관련 의존성만이 남았다. (+lombok)

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")
  • (1) : kotlin 플러그인 여러개가 사용되므로 gradle.properties 에서 작성한 버전을 공통으로 사용하도록 한다. 여기에 작성하지 않고 build.gradle 파일에 작성해도 무관하다.
  • (2) : 각 추가한 모듈을 모두 include 처리해야 root project 에서 한 번에 빌드할 수 있다. intelliJ 를 통해 모듈을 추가했다면 자동으로 작성되어 있을 것이다. 혹시 누락되어 있다면 작성해주자.

5. 마무리

이제 gradle 을 빌드하면 아래와 같이 의존성이 잘 설정된 것을 확인할 수 있다.

  • kotlin 관련 의존성은 모든 모듈에 공통으로 존재한다.(root project 에 작성했기 때문)
  • jpa 관련 의존성은 도메인 모듈에만 존재하고(api 가 아닌 implementation 을 사용했기 때문)
  • web 관련 의존성은 ERP 모듈에만 존재한다.
  • 또한 ERP 모듈은 도메인 모듈과 common 모듈을 의존한다.

오예! 이제 개발을 시작할 기본적인 준비가 완료됐다.
이제부터는 즐거운 개발 시간이다.

관련 전체 코드는 Github 에서 확인할 수 있다.

profile
더 빠르게 더 많이 성장하고 싶은 개발자입니다

0개의 댓글