본문 바로가기

Spring

[SpringBoot] Hello, Multimodule! - 멀티 모듈 설정

1. 멀티 모듈?

  • 모듈: 프로그램을 구성하는 시스템을 기능 단위로 분할한 것
  • 멀티 모듈: 하나의 프로젝트를 모듈 단위로 분리한 것

 

멀티 모듈을 사용하여 프로그램을 설계하면 시스템을 역할과 책임 단위로 분리할 수 있어 유지보수성이 증가하고, 코드의 중복도 제거하여 코드를 깔끔하게 작성할 수 있다.

2. 아키텍쳐

초기의 멀티 모듈 아키텍쳐는 다음과 같이 간단하게 설계하였다.

  • api: controller와 service로 구성, api 요청을 받아 이를 처리하는 작업 수행
  • dbmodule: domain, dto, repository로 구성, 주로 도메인을 dao를 통해 관리하는 작업 수행

다음 프로젝트에도 이를 적용하고, 필요 시 설계를 확장 및 분리할 계획에 있다.

 

3. 디렉토리 구조

메인 프로젝트의 디렉토리이다. 다른 모듈들을 담는 그릇 정도의 역할을 하고, 이에 따라 src와 메인 애플리케이션이 존재하지 않는다.

api 모듈의 디렉토리이다. 컨트롤러, 서비스와 컴포넌트 스캔을 위한 config 패키지가 포함되어 있다. 컨트롤러로 사용자의 요청을 직접 받아오므로 메인 애플리케이션은 여기에 존재한다.

db 모듈의 디렉토리이다. 아키텍처에서 언급한 dto, 도메인, 리포지토리가 포함되어 있다.

3. gradle 파일

  3.1 메인 프로젝트 설정

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.3.3'
	id 'io.spring.dependency-management' version '1.1.6'
}

bootJar { enabled = false }
jar { enabled = true }



allprojects{
	group = 'com'
	version = '0.0.1-SNAPSHOT'
	sourceCompatibility = '17'

	repositories {
		mavenCentral()
	}


}

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}


// setting.gradle에서 include된 모든 프로젝트에 공통적으로 적용할 설정
subprojects{

	apply plugin: "java"
	apply plugin: 'java-library'
	apply plugin: "io.spring.dependency-management"
	apply plugin: "org.springframework.boot"



	configurations {
		compileOnly {
			extendsFrom annotationProcessor
		}
	}

	dependencies {

		compileOnly 'org.projectlombok:lombok'
		annotationProcessor 'org.projectlombok:lombok'
		annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

		testImplementation 'org.springframework.boot:spring-boot-starter-test'
		testCompileOnly 'org.projectlombok:lombok'
		testAnnotationProcessor 'org.projectlombok:lombok'


	}

	test {
		useJUnitPlatform()
	}

}

메인 프로젝트의 build.gradle 파일이다. 멀티 프로젝트 관련 파일을 간단히 알아보자.

bootJar { enabled = false }
jar { enabled = true }
  • bootJar: dependencies와 클래스 파일을 모두 묶어서 실행 가능한 jar 파일로 빌드
  • jar: 클래스만 묶어서 일반 jar 파일로 빌드

루트 경로에서는 애플리케이션이 없으므로 jar로 설정한다.

allprojects{
	group = 'com'
	version = '0.0.1-SNAPSHOT'
	sourceCompatibility = '17'

	repositories {
		mavenCentral()
	}


}

루트 경로를 포함한 모든 프로젝트의 설정이다. 그룹명과 자바 호환 버전 설정, gradle용 기본 리포지토리 지정이 명시되어있다.

// setting.gradle에서 include된 모든 프로젝트에 공통적으로 적용할 설정
subprojects{

	apply plugin: "java"
	apply plugin: 'java-library'
	apply plugin: "io.spring.dependency-management"
	apply plugin: "org.springframework.boot"



	configurations {
		compileOnly {
			extendsFrom annotationProcessor
		}
	}

	dependencies {

		compileOnly 'org.projectlombok:lombok'
		annotationProcessor 'org.projectlombok:lombok'
		annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

		testImplementation 'org.springframework.boot:spring-boot-starter-test'
		testCompileOnly 'org.projectlombok:lombok'
		testAnnotationProcessor 'org.projectlombok:lombok'


	}

서브 프로젝트(여기서는 모듈)에 적용할 설정이다. 모든 모듈에서 사용할 스프링부트, 롬복을 설정하고, 이를 위해 annotationProcessor를 설정한다.

rootProject.name = 'multimoduletest'

include 'dbmodule', 'api'

settings.gradle 파일이다. 사용할 모듈을 등록해준다.

  3.2 api 모듈

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.3'
    id 'io.spring.dependency-management' version '1.1.6'
}


java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    implementation project(':dbmodule')
}

tasks.named('test') {
    useJUnitPlatform()
}

api 모듈의 build.gradle이다. spring web을 등록해주고, repository 함수의 사용을 위해 dbmodule의 의존성을 추가해준다. 

 3.3 dbmodule

bootJar {enabled = false}
jar {enabled = true}


dependencies {

    runtimeOnly 'org.postgresql:postgresql'
    api 'org.springframework.boot:spring-boot-starter-data-jpa'
}

tasks.named('test') {
    useJUnitPlatform()
}

 dbmodule 모듈의 build.gradle이다. db 연결을 위해 postgresql을 넣고, 스프링데이터 JPA를 추가했다.

    3.3.1 관련 트러블슈팅

  api 모듈에서 리포지토리의 코드를 불러올 수 없는 문제가 발생했다. 원인을 확인해 본 결과 dbmodule에 스프링데이터 jpa를 등록할 때 implementation을 사용하여 의존성을 추가하여 발생한 문제였다.

api는 dbmodule을 의존, dbmodule는 스프링데이터 jpa를 의존하고 있다. 이를 transitive dependency라 한다. dbmodule에서 implementation을 사용하면 transitive dependency를 불허하여 api에서 해당 기능을 사용할 수 없다.

 반면 api를 사용하게 되면 이를 허용하여 api 모듈에서 스프링데이터 jpa에서 제공하는 함수를 이용할 수 있게 되는 것이다.

 

4. 맺음

 이로서 기본 설정은 끝이다. 다음 포스팅에서는 본격적으로 코드와 application.yml을 작성해보고, 그 과정에서 발견한 문제점들을 다룰 것이다.