새소식

Java

[Spring] Spring Boot 에서 멀티 모듈 구현

  • -
728x90
간혹 비즈니스 로직이나 스케줄러 + 페이지 이렇게 2개의 프로젝트를 구현해야 하는 경우가 생기곤 한다. IntellJ 에서 각각을 실행시키고 관리도 별도로 진행해야 하기 때문에 항상 번거로움이 생긴다. 이를 효과적으로 관리하고 추후에도 다른 용도로도 도움이 될 것 같아, 현재 진행하는 프로젝트는 멀티 모듈로 구현하고 이 과정을 기록한다.

 

Spring Boot에서 멀티 모듈 프로젝트는 하나의 큰 프로젝트를 더 관리하기 쉽고 모듈화된 여러 하위 프로젝트로 나누는 것을 의미한다. 이 접근법은 각 모듈이 독립적으로 개발되고 관리될 수 있도록 해서, 대규모 애플리케이션의 복잡성을 줄이고 유지보수를 용이하게 만들 수 있다. 각 모듈은 특정 비즈니스 로직이나 데이터베이스 작업, 서비스 API 등을 처리할 수 있으며, 필요한 경우 다른 모듈들과 연동할 수 있다.

 

멀티 모듈 프로젝트의 구조와 이점

분리된 관심사

- 각 모듈은 특정 기능을 담당하므로 코드의 관심사가 분리되어, 각 팀이나 개발자는 자신들의 모듈만을 집중적으로 관리할 수 있다.

 

재사용성

- 공통 기능을 갖는 모듈은 여러 프로젝트에서 재사용될 수 있어 코드의 중복을 줄이고 개발 시간을 단축할 수 있다.

 

독립적인 빌드와 배포

- 각 모듈은 독립적으로 빌드하고 배포할 수 있으므로, 특정 기능에 문제가 생겼을 때 전체 시스템을 재배포하지 않아도 된다.

 

1. 프로젝트 구성

현재 진행하는 프로젝트에서 선착순 쿠폰 발급 시스템을 구현하고 있다. Gradle-Kotlin 기반으로, 이를 3개의 모듈로 분리할 것이고 각각의 이름과 용도는 아래와 같다.

 

coupon-core
- api 와 consumer 에 대한 공통적인 처리

coupon-api
- 쿠폰에 대한 api 서버, 유저에 대한 요청

coupon-consumer
- 쿠폰의 비동기적인 발급, 처리

 

패키지 구성

 

위와 같이 각각의 모듈을 구성했고 기존의 src 디렉토리를 제거해 주었다. 루트모듈은 하위 모듈을 담는 그릇처럼 사용되고 루트 모듈 내 src 디렉토리는 아무런 역할을 하지 못하기때문에 제거해도, 그대로 둬도 무방하다! 이제는 루트의 build.gradle.kts 를 아래와 같이게 만들 수 있다.

 

settings.gradle.kts

rootProject.name = "coupon"
include("coupon-core","coupon-api","coupon-consumer")

 

coupon-api 와 consumer 가 core 를 import 해서 사용하는, 의존적인 형태의 구조로 프로젝트를 설계했다. 이를 위해 CouponConsumerApplication 를 아래와 같이 수정했고, 이는 CouponApiApplication 도 동일하다!

 

[CouponConsumerApplication]

@Import(CouponCoreConfiguration.class)
@SpringBootApplication
public class CouponConsumerApplication {
    public static void main(String[] args) {
        System.setProperty("spring.config.name", "application-core,application-consumer");
        SpringApplication.run(CouponConsumerApplication.class, args);
    }
}

 

`System.setProperty("spring.config.name", "application-core,application-consumer");` 은 Java 시스템 프로퍼티를 설정하는 메소드를 사용하여, 스프링 부트(Spring Boot) 애플리케이션에서 사용할 구성 파일의 이름을 지정하는 방법이다.

이를 통해 JVM(Java Virtual Machine)의 시스템 프로퍼티를 직접 설정하며, 이는 스프링 부트가 구성 파일을 로드할 때 참조하는 값이다.

 

🔻 작동 방식

- `System.setProperty`는 Java 시스템의 키-값 쌍 프로퍼티를 설정한다. 이 경우, `spring.config.name`이라는 키에 `application-core,application-consumer`라는 값을 할당한다.
- `spring.config.name`은 스프링 부트가 애플리케이션을 시작할 때 읽어들일 구성 파일의 기본 이름을 지정한다. 기본적으로 스프링 부트는 `application.properties` 또는 `application.yml` 파일을 구성 파일로 사용한다. 하지만 이 프로퍼티를 사용하여 다른 이름의 파일을 구성 파일로 지정할 수 있다.


🔻 사용 목적

이 설정은 특히 복수의 구성 파일을 사용하는 복잡한 애플리케이션에서 유용하다고 해서 사용했다. 예를 들어, 여러 모듈 또는 서비스가 있는 경우 각각의 모듈이나 서비스에 특화된 구성 파일을 가질 수 있다. 이를 통해 `application-core.properties`, `application-consumer.properties` 등과 같이 여러 구성 파일을 관리할 수 있으며, 스프링 부트는 이 파일들을 읽어 설정을 적용한다.

 

💡정리하면 CouponCosumerApplication 은 application-core.yml 과 application-consumer.yml 파일을 구성파일로 사용하며 이를 통해 application-core 에 작성된 변수들을 읽는 것이 가능해진다. 

(active-profile 을 사용할 경우, 이를 지정하지 않으면 setProperty 를 했음에도 조회가 되지 않을 수 있으니 주의해야 한다.)

 

2. 빌드 스크립트

루트 모듈의 build.gradle.kts 에 작성한 내용이다. 자세한 내용은 주석으로 첨부했다.

 

val bootJar: org.springframework.boot.gradle.tasks.bundling.BootJar by tasks

// 빌드시 현재 모듈(multi-module)의 .jar를 생성하지 않는다.
bootJar.enabled = false

plugins {
    java
    id("org.springframework.boot") version "3.2.5"
    id("io.spring.dependency-management") version "1.1.4"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_17
}

configurations {
    compileOnly {
        extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {
    mavenCentral()
}

// 모든 하위 모듈들에 이 설정을 적용
subprojects {
    apply(plugin = "java")
    apply(plugin = "io.spring.dependency-management")
    apply(plugin = "org.springframework.boot")

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation("org.springframework.boot:spring-boot-starter-data-jpa")
        compileOnly("org.projectlombok:lombok")
        annotationProcessor("org.projectlombok:lombok")
        runtimeOnly("com.h2database:h2")
        runtimeOnly("com.mysql:mysql-connector-j")
        implementation("org.springframework.boot:spring-boot-starter")

        // querydsl
        implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
        annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jakarta")
        annotationProcessor("jakarta.annotation:jakarta.annotation-api")
        annotationProcessor("jakarta.persistence:jakarta.persistence-api")
    }
}
tasks.withType<Test> {
    useJUnitPlatform()
}

 

coupon-core`s build.gradle.kts

// bootJar 태스크를 tasks 컬렉션에서 찾아서 bootJar 변수에 할당한다
val bootJar: org.springframework.boot.gradle.tasks.bundling.BootJar by tasks

// bootJar 태스크를 비활성화한다. 즉, `bootJar` 태스크가 실행되지 않도록 설정
// 이는 JAR 파일 생성을 막는 데 사용될 수 있다
bootJar.enabled = false

// 프로젝트에서 사용할 저장소를 지정
repositories {
    // Maven Central 저장소를 사용하도록 설정
    // Maven Central은 많은 오픈소스 라이브러리들이 호스팅되는 중앙 저장소다.
    mavenCentral()
}

dependencies {
    implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
    implementation("com.fasterxml.jackson.core:jackson-databind")
    implementation("org.redisson:redisson-spring-boot-starter:3.16.4")
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("com.github.ben-manes.caffeine:caffeine")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

// 모든 Test 타입의 태스크를 구성
tasks.withType<Test> {
    // JUnit 플랫폼을 사용하도록 테스트 태스크를 설정합니다. 이는 JUnit 5를 사용하여 테스트를 실행하는 데 필요합니다.
    useJUnitPlatform()
}

 

coupon-api`s build.gradle.kts

이는 coupon-core 모듈에 의존하고 있으므로 아래와 같이 표기

dependencies {
    implementation(project(":coupon-core"))
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

Gradle

 

위와 같은 과정들을 통해 간단한 멀티 모듈 프로젝트를 생성하여 모듈간 의존성을 설정해 보았다. 현재 진행중인 프로젝트에 포스팅할 내용이 너무나도 많다. 이를 주기적으로 업데이트하며 멀티모듈을 통한 프로젝트 전체에 대해 소개하는 글을 작성하고자 한다.

 

728x90
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.