배경
https://songacoding.tistory.com/199
- MSA 구조로 백엔드 spring 서버를 여러 모듈로 나누어 구축하려함
1. 멀티 모듈이란?
• 모듈: 독립적으로 배포될 수 있는 코드의 단위
• 멀티 모듈 프로젝트: 하나의 Root Module(부모)과 여러 개의 Sub Module(자식)로 구성된 프로젝트
• Root Module은 프로젝트의 전체적인 설정을 관리하고, Sub Module은 각 서비스별로 기능을 담당함
- 루트 프로젝트는 껍데기 역할만 하고 실제 실행되는 애플리케이션은 하위 모듈들
2. 프로젝트 구성
- Root Module 생성
• start.spring.io에서 프로젝트를 생성
• Dependency에 Spring Web 추가
- Sub Module 추가
• Sub Module을 만들고 dependency를 제거한 상태로 생성
• 예: a-module, b-module
- 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 새로고침 실행
- Root Module의 설정
• Root Module에서 Sub Module의 빌드와 설정을 관리
• Root Module의 build.gradle을 다음과 같이 구성
- 의존성 추가 범위는 총 3가지 방법이 있습니다
- allproject (추천하지 않음)루트 프로젝트에도 적용되기 때문에 멀티모듈에서 불필요한 설정이 포함될 가능성이 높음.
- 우리 프로젝트에서 루트는 껍데기이니 사용하지 않음
- 모든 프로젝트(루트 프로젝트 + 하위 모듈)에 공통 설정을 적용하는 방법입니다.
- subproject루트 프로젝트에는 영향을 주지 않으면서 모든 하위 모듈에 공통 설정을 추가할 수 있음.
- 서브프로젝트는 저만 건듭니다
- 하위 모듈에만 공통 설정을 적용하는 방법입니다.
- project(':api-gateway')subprojects {} 로 공통 설정을 적용하되, 개별 모듈에서만 다른 설정을 추가해야 할 때 사용합니다.
- 여기서 개별 모듈 개발시 의존성 추가하세요
- 특정 모듈에만 적용하고 싶을 때 사용하는 방법입니다.
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'
}
}
유틸 모듈에서 사용할 공통 요소 예시
유틸 모듈에서 사용할 공통 요소 예시
- 공통 응답 처리 (Response Format)
• API 응답을 표준화하기 위한 CommonResponse 클래스를 정의
• 성공/실패 여부, 메시지, 데이터 등을 포함
- 예외 처리 (Global Exception Handling)
• 공통적으로 사용할 CustomException 클래스
• ExceptionHandler를 통해 예외 응답을 표준화
- 로깅 (Logging)
• 공통적으로 사용할 LoggerUtil 클래스
• 요청/응답 로그 및 에러 로그 포맷팅
- DTO 변환 유틸리티 (DTO Mapper)
• Object 변환 (Entity ↔ DTO)을 돕는 ModelMapperUtil
- JWT 및 보안 관련 유틸리티
• JWT 토큰 발급, 검증을 위한 JwtUtil
• Spring Security 관련 공통 설정
- 공통 상수 (Constants)
• API 응답 메시지, 에러 코드, 역할(Role) 등을 정의한 Constants 클래스
- 유효성 검사 (Validation)
• 공통적으로 사용할 ValidatorUtil
• 이메일 형식, 패스워드 복잡도 체크
- 날짜 및 시간 유틸리티 (DateUtil)
• 시간 변환, 포맷팅 등 공통적으로 필요한 DateUtil
- Redis 관련 유틸리티
• Redis를 이용한 캐싱, 세션 관리 유틸
- 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 |