본문 바로가기
Spring

Spring MSA 멀티 모듈 레포 구성 방법

by HanYaung 2025. 3. 27.

배경

https://songacoding.tistory.com/199

  • MSA 구조로 백엔드 spring 서버를 여러 모듈로 나누어 구축하려함

 

1. 멀티 모듈이란?

모듈: 독립적으로 배포될 수 있는 코드의 단위

멀티 모듈 프로젝트: 하나의 Root Module(부모)과 여러 개의 Sub Module(자식)로 구성된 프로젝트

Root Module은 프로젝트의 전체적인 설정을 관리하고, Sub Module은 각 서비스별로 기능을 담당함

  • 루트 프로젝트는 껍데기 역할만 하고 실제 실행되는 애플리케이션은 하위 모듈들

2. 프로젝트 구성

  1. Root Module 생성

• start.spring.io에서 프로젝트를 생성

• Dependency에 Spring Web 추가

  1. Sub Module 추가

• Sub Module을 만들고 dependency를 제거한 상태로 생성

• 예: a-module, b-module

  1. Sub Module을 Root Module에 포함

• 서브 모듈 폴더를 Root Module로 드래그 앤 드롭

• settings.gradle에서 다음과 같이 서브 모듈을 추가

  • 이를 통해서 하위 모듈을 Gradle이 인식하게 됩니다.
  • 모듈로 인식이 되면 아래와 같은 파란색 네모가 파일구조에서 보입니다

rootProject.name = 'BE'

// 멀티 모듈 설정
include 'api-gateway'
include 'eureka-server'
include 'user-service'
include 'stock-service'
include 'snowflake-service'
include 'util-service'

• 이후 Gradle 새로고침 실행

  1. Root Module의 설정

• Root Module에서 Sub Module의 빌드와 설정을 관리

• Root Module의 build.gradle을 다음과 같이 구성

  • 의존성 추가 범위는 총 3가지 방법이 있습니다
    1. allproject (추천하지 않음)루트 프로젝트에도 적용되기 때문에 멀티모듈에서 불필요한 설정이 포함될 가능성이 높음.
    2. 우리 프로젝트에서 루트는 껍데기이니 사용하지 않음
    3. 모든 프로젝트(루트 프로젝트 + 하위 모듈)에 공통 설정을 적용하는 방법입니다.
    4. subproject루트 프로젝트에는 영향을 주지 않으면서 모든 하위 모듈에 공통 설정을 추가할 수 있음.
    5. 서브프로젝트는 저만 건듭니다
    6. 하위 모듈에만 공통 설정을 적용하는 방법입니다.
    7. project(':api-gateway')subprojects {} 로 공통 설정을 적용하되, 개별 모듈에서만 다른 설정을 추가해야 할 때 사용합니다.
    8. 여기서 개별 모듈 개발시 의존성 추가하세요
    9. 특정 모듈에만 적용하고 싶을 때 사용하는 방법입니다.
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.2'
    id 'io.spring.dependency-management' version '1.1.5'
}

repositories {
    mavenCentral()
    maven { url '<https://repo.spring.io/release>' }
    maven { url '<https://repo.spring.io/milestone>' }
}

// ✅ 루트 프로젝트는 실행되지 않고, 설정만 제공하는 역할
// 하위 모듈에만 공통 설정 적용
subprojects {
    apply plugin: 'java'
    apply plugin: 'io.spring.dependency-management'
    apply plugin: 'org.springframework.boot'

    group = 'com.pda'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '17'

    repositories {
        mavenCentral()
        maven { url '<https://repo.spring.io/release>' }
        maven { url '<https://repo.spring.io/milestone>' }
    }

    dependencyManagement {
        imports {
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:2023.0.0"
        }
    }

    dependencies {
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
        compileOnly 'org.projectlombok:lombok'
        annotationProcessor 'org.projectlombok:lombok'
    }

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

// ✅ API Gateway 모듈
project(':api-gateway') {
    dependencies {
        implementation project(':util-service')
        implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
        implementation 'org.springframework.boot:spring-boot-starter-security'
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
        implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
        implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    }
}

// ✅ Eureka Server 모듈
project(':eureka-server') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation project(':util-service')
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
        implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    }
}

// ✅ User Service 모듈
project(':user-service') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation project(':util-service')
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
        implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
        implementation 'org.springframework.boot:spring-boot-starter-security'
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-validation'
        implementation 'org.springframework.boot:spring-boot-starter-mail'
        implementation 'mysql:mysql-connector-java:8.0.33'
        implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'

        // OAuth2 + Refresh Token
        implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
        implementation 'org.springframework.security:spring-security-oauth2-jose'
    }
}

// ✅ Stock Service 모듈
project(':stock-service') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation project(':util-service')
        implementation 'mysql:mysql-connector-java:8.0.33'
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    }
}

// ✅ Snowflake Service 모듈
project(':snowflake-service') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation project(':util-service')
        implementation 'mysql:mysql-connector-java:8.0.33'
        implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
        implementation 'org.springframework.cloud:spring-cloud-starter-loadbalancer'
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
    }
}

// ✅ Util Service 모듈 (라이브러리 역할, 실행되지 않음)
project(':util-service') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter'
        implementation 'com.fasterxml.jackson.core:jackson-databind'
        implementation 'org.apache.commons:commons-lang3'
    }

    bootJar {
        enabled = false  // 실행 가능한 JAR 생성하지 않음
    }

    jar {
        enabled = true
        archiveBaseName = 'util-service'
    }
}

 

3. 개별 모듈 의존성 추가 방법

  • 방법1 : 루트 모듈의 build.gradle에서 공통 설정은 subprojects, 개별 설정은 project(하위모듈)에 설정하는법
    • 추천 방법: 유지보수성이 높고, 개별 모듈별로 의존성을 쉽게 추가 가능
  • 방법2 : 루트 모듈의 build.gradle에서 공통 설정은 subprojects, 개별 설정은 개별 모듈안의 각각의 build.gradle에 의존성 추가하는 방법
    • 이 방법은 여러 설정 파일을 관리해야해서 충돌이 자주 일어남
    • 하나의 파일로 의존성 관리를 할 수 없음

✅ 1번 방법 vs 2번 방법 비교

방법 장점 단점

방법1 (추천) - build.gradle 하나에서 모든 설정 관리 가능  - 충돌 방지 및 유지보수 용이 - 개별 모듈에 추가할 의존성을 루트 build.gradle에서 따로 지정해야 함
방법2 (비추천) - 각 모듈별 build.gradle에서 개별적으로 관리 가능 - build.gradle이 여러 개라서 충돌 가능성 증가  - 전체적인 의존성 관리가 어려움

 

 

유틸 모듈을 다른 모듈들 묶어서 같이 사용하는 방법

  • 공통 모듈 사용 이유
    • 공통으로 사용하는 모듈을 하나 만들어 놓고 공통으로 사용하려 함
    • Utility와 A와 같이 빌드하고, Utility와 B와 같이 빌드하고, Utility와 C와 같이 빌드하고,
    • Utility에 회원을 넣어도 될까? → 공식적으로 안된다, 하지말자

📌 Util 모듈을 특정 모듈과 묶는 방법

방법:

• Util 모듈을 User, Stock, Snowflake 등의 모듈과 묶어서 하나의 모듈 그룹으로 관리

• 예를 들어, User 모듈과 Util 모듈을 묶어서 user-service가 Util 기능을 포함하도록 할 수 있음

• 방법은 Gradle의 dependencies에서 project(':util-service')를 추가하는 방식

1️⃣ Root settings.gradle 수정 (서브모듈 포함)

  • multi-module이 sub module들을 인식
rootProject.name = 'multi-module-project'
include 'api-gateway', 'eureka-server', 'user-service', 'stock-service', 'snowflake-service', 'util-service'

2️⃣ root-project/build.gradle Util 모듈 포함

project(':user-service') {
    dependencies {
        implementation project(':util-service')  // Util 모듈 추가
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.springframework.boot:spring-boot-starter-security'
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    }
}

유틸 모듈에서 사용할 공통 요소 예시

 

유틸 모듈에서 사용할 공통 요소 예시

  1. 공통 응답 처리 (Response Format)

• API 응답을 표준화하기 위한 CommonResponse 클래스를 정의

• 성공/실패 여부, 메시지, 데이터 등을 포함

  1. 예외 처리 (Global Exception Handling)

• 공통적으로 사용할 CustomException 클래스

• ExceptionHandler를 통해 예외 응답을 표준화

  1. 로깅 (Logging)

• 공통적으로 사용할 LoggerUtil 클래스

• 요청/응답 로그 및 에러 로그 포맷팅

  1. DTO 변환 유틸리티 (DTO Mapper)

• Object 변환 (Entity ↔ DTO)을 돕는 ModelMapperUtil

  1. JWT 및 보안 관련 유틸리티

• JWT 토큰 발급, 검증을 위한 JwtUtil

• Spring Security 관련 공통 설정

  1. 공통 상수 (Constants)

• API 응답 메시지, 에러 코드, 역할(Role) 등을 정의한 Constants 클래스

  1. 유효성 검사 (Validation)

• 공통적으로 사용할 ValidatorUtil

• 이메일 형식, 패스워드 복잡도 체크

  1. 날짜 및 시간 유틸리티 (DateUtil)

• 시간 변환, 포맷팅 등 공통적으로 필요한 DateUtil

  1. Redis 관련 유틸리티

• Redis를 이용한 캐싱, 세션 관리 유틸

  1. Kafka 메시징 유틸

 

예시 파일구조

하위 모듈 build.gradle

  • 삭제하기

하위 모듈 setting.gradle

  • 삭제하기

'Spring' 카테고리의 다른 글

[오류해결] jpa N+1 문제  (0) 2025.03.26
[오류해결] Spring security CORS 문제  (0) 2025.03.25
Spring Cloud Eureka MSA 사용법  (0) 2025.03.06