관리 메뉴

피터의 개발이야기

[Kotlin] Spring Boot 멀티모듈 프로젝트 구성 본문

Programming/Kotlin

[Kotlin] Spring Boot 멀티모듈 프로젝트 구성

기록하는 백앤드개발자 2024. 8. 18. 10:10
반응형

ㅁ 들어가며

ㅇ api, batch, gateway, internel...를 구성하면서, 반복되는 Util, Dto, Entity, Service, Enum...을 만나게 된다.

ㅇ 동일한 파일을 여러번 변경하기도 하고, 프로젝트별로 변경된 Util은 공통으로 관리하기도 어렵다.

ㅇ 예로, 프로젝트가 10개이면, dto 하나 수정하고 PR 10번 올려야 한다.

ㅇ 공통 기능을 모듈화하여 코드의 재사용성을 높이고 의존성 관리를 개선하기 위해 멀티 모듈은 꼭!! 필요하다.

ㅇ 이번 글에서는 Kotlin Spring Boot 프로젝트를 멀티모듈로 구성하는 방법을 정리하였다. 

 

ㅁ 멀티 모듈 프로젝트의 필요성

코드 재사용성

  - 공통 기능을 개별 모듈에서 분리하여 공통으로 만들면 재사용이 가능하다.

  - 중복코드를 줄이고, 유지보수를 용이하게 한다.

관심사 분리

  - 각 모듈의 기능이나 도메인에 집중하여 코드의 구조를 단순화하고 관리가 쉬워진다.

의존성 관리 개선

  - 각 모듈의 의존성을 명확히 정의하고 관리할 수 있다.
  - 불필요한 의존성을 줄이고 모듈 간 결합도를 낮출 수 있다.

배포의 용이성

  - 모듈은 독립적으로 배포될 수 있는 코드의 단위를 말하며, 이러한 코드 뭉치를 멀티 모듈이라고 한다.

  - 특정 기능만 업데이틀를 하거나 롤백할 수 있다.

 

ㅁ 프로젝트 구조

ㅇ 멀티 모듈 프로젝트는 일반적으로 다음과 같은 구조를 가진다

kotlin-multi-module
│
├── build.gradle.kts
├── settings.gradle.kts
│
├── core
│   ├── build.gradle.kts
│   └── src/main/kotlin/com/peterica/core
│
├── rest
│   ├── build.gradle.kts
│   └── src/main/kotlin/com/peterica/rest
│
└── domain
    ├── build.gradle.kts
    └── src/main/kotlin/com/peterica/domain

ㅇ domain은 dto, enum, error code와 같은 기초 정보를 담고 있다.

ㅇ core는 정보를 핸들링하는 서비스 영역을 그룹화 하여 batch나 api에서 service를 재사용할 수 있도록 한다.

ㅇ rest는 외부에 서비스를 제공하는 역할을 한다.

ㅇ rest는 core를, core는 domain에 의존하는 형태로 사용된다. 

ㅇ 반대로  rest, core, domain 순으로 재사용성이 높다.

ㅇ 우선 루트 프로젝트를 생성하고 모듈의 골격을 만들어 보자.

 

ㅁ 루트 프로젝트 생성

ㅇ IntelliJ IDEA를 사용하여 새로운 Gradle 프로젝트를 생성한다. 

ㅇ 프로젝트 이름은 kotlin-multi-module로 설정한다.

ㅇ (옵션) MySQL과 JPA 종속성을 추가하였다.

 

ㅁ 서브모듈 폴더 생성

ㅇ 루트 프로젝트 내에 새로운 디렉토리를 만들어 각 모듈을 생성한다 (예: rest, core, domain)
ㅇ 각 모듈 디렉토리에 `build.gradle.kts` 파일을 생성한다.

ㅇ root의 src는 제거한다.

 

ㅁ 루트 build.gradle.kts 설정

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.js.translate.context.Namer.kotlin as kotlin

plugins {
    id("java")
    id("org.springframework.boot") version "3.2.5" apply false
    id("io.spring.dependency-management") version "1.1.4" apply false
    id("org.hibernate.orm") version "6.4.9.Final"

    kotlin("jvm") version "1.9.23" apply(false)
    kotlin("plugin.jpa") version "1.9.23"
    kotlin("plugin.spring") version "1.9.23"
    kotlin("kapt") version "1.9.23"
}


configure(allprojects) {
    repositories {
        mavenCentral()
    }
}
group = "com.peterica"
version = "0.0.1-SNAPSHOT"


configure(subprojects) {
    apply {
        plugin("org.springframework.boot")
        plugin("kotlin")
        println("java")
        plugin("io.spring.dependency-management")
    }

    dependencies {
        implementation("org.springframework.boot:spring-boot-starter")
        implementation("org.springframework.boot:spring-boot-starter-data-jpa")
        implementation("org.jetbrains.kotlin:kotlin-reflect")

        runtimeOnly("com.mysql:mysql-connector-j")

        testImplementation("org.springframework.boot:spring-boot-starter-test")
        testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
        testRuntimeOnly("org.junit.platform:junit-platform-launcher")
    }

    tasks.withType<Test> {
        useJUnitPlatform()
    }
}

ㅇ 공통 플러그인과 의존성을 설정한다.
ㅇ subprojects 블록을 사용하여 모든 서브모듈에 공통 설정을 적용한다.

ㅇ 모듈 개별 설정은 모듈의 build.gradle.kts 설정하면 된다. 

 

ㅁ 모듈 build.gradle.kts 설정

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

ㅇ 샘플로 3개 모듈 동일하게 적용하였다. 

ㅇ 루트에서 적용한 공통 설정 외에 개별 모듈에서 필요한 의존성을 추가다.

ㅇ 모듈 작성이 완료되면 서브모듈을 Gradle에 인식시켜야 한다.

 

ㅁ settings.gradle.kts 설정

rootProject.name = "kotlin-multi-module"
include("rest", "core", "domain")

ㅇ 루트 프로젝트의 settings.gradle.kts에 서브모듈을 포함시킨다.

ㅇ Gradle에 서브모듈을 인식 시키게 된다.

ㅇ Gradle의 Reload 버튼을 클릭하면 신규 모듈이 나타나고, 모듈의 Task가 별도로 설정된다.


ㅁ 모듈 간 의존성 설정

ㅇ 지금까지 물리적 공간적 모듈 설정이었다면, Gradle에 논리적인 모듈 설정을 해야한다.

ㅇ 필요한 경우 모듈 간 의존성을 설정한다. 

ㅇ rest모듈은 core모듈과 domain모듈에 의존한다.

 

ㅁ 애플리케이션 구조화

ㅇ 각 모듈의 패키지 구조를 설계하고 코드를 적절히 배치해야 한다.

ㅇ 모듈의 종류와 그룹이 많아지면, 서로 간에 의존성이 생기게 된다.

ㅇ 현재의 멀티모듈은 간단해서 이런 일이 거의 없겠지만, 규모가 커지면 다음과 같은 에러가 발생한다.

Circular dependency between the following tasks:
:core:classes
\--- :core:compileJava
     +--- :core:compileKotlin
     |    +--- :core:kaptKotlin
     |    |    \--- :core:kaptGenerateStubsKotlin
     |    |         \--- :domain:jar
     |    |              +--- :domain:classes
     |    |              |    \--- :domain:compileJava
     |    |              |         +--- :core:jar
     |    |              |         |    +--- :core:classes (*)
     |    |              |         |    +--- :core:compileJava (*)
     |    |              |         |    +--- :core:compileKotlin (*)
     |    |              |         |    \--- :core:kaptKotlin (*)
     |    |              |         \--- :domain:compileKotlin
     |    |              |              +--- :core:jar (*)
     |    |              |              \--- :domain:kaptKotlin
     |    |              |                   \--- :domain:kaptGenerateStubsKotlin
     |    |              |                        \--- :core:jar (*)
     |    |              +--- :domain:compileJava (*)
     |    |              +--- :domain:compileKotlin (*)
     |    |              \--- :domain:kaptKotlin (*)
     |    \--- :domain:jar (*)
     \--- :domain:jar (*)

ㅇ core는 domain을 domain은 core를 서로 참조하면서 순환 참조 오류가 발생한다.

ㅇ 이런 경우를 위해 common모듈을 추가해 보자.

 

common모듈 추가

ㅇ 물리적 폴더 구조를 생성하고, gradle에 모듈을 인식시킨다.

ㅇ 그리고 마지막으로 참조하는 모듈의 의존성을 선언한다.

 

ㅁ 빌드 및 실행

ㅇ 루트 디렉토리에서 gradle build하면 전체 프로젝트가 빌드되면서 모듈별 jar가 형성된다.
ㅇ 이 방식으로 코드를 모듈화하면 관심사를 분리하고, 재사용성을 높이며, 프로젝트 구조를 더 명확하게 만들 수 있다.

 

ㅁ 함께 보면 좋은 사이트

SpringDoc- Creating a Multi Module Project

[Spring Boot] Kotlin Querydsl 적용 (with. Mono repo, Multi module)

 git - kotlin-spring-multi-module-example

[Spring] 스프링부트로 멀티 모듈 프로젝트 진행하기

Spring Boot + kotlin 프로젝트에 ktlint 적용하기 (Multi module 통합 관리하기)

실전! 멀티 모듈 프로젝트 구조와 설계 - 김대성

   ㄴ 설계의 방향성을 잘 설명함.

반응형
Comments