MSA 프로젝트를 구성하다보니 기존 모놀리식에선 신경쓰지 못했던 문제들이 엄청나게 발생하는데, 그 중 하나가 중복되는 코드의 문제였습니다.
ResponseEntity
, ExceptionHandler
등의 모든 서버에서 쓰이는 로직은 서버 전부에서 코드를 가지고 있어야 했고, 복사-붙여넣기의 과정은 한쪽이 변경되면 다른쪽도 변경해줘야하는 귀찮음과 오타의 위험이 있었습니다.
또한 IDE를 3-4개씩 띄워두고 일일히 서버를 돌리는 것도 굉장히 불편했기 때문에, 멀티 프로젝트로 구성되어있던 서비스를 멀티 모듈로 전환하기로 결정했습니다.
멀티 모듈이란 서로 독립적인 프로젝트를 하나의 프로젝트로 묶어 모듈로서 사용되는 구조를 말합니다.
멀티 모듈을 사용하면 공통적인 기능을 모아 하나의 모듈(common)로 만들고, 각 서버에서 import 하여 사용하는 것이 가능합니다.
즉, 모든 서버에서 공통으로 사용하는 util, exception 등을 모듈로 분리해 사용할 수 있는 것입니다.
코드의 중복을 줄일 수 있다.
공통된 로직이 있는 여러 서비스를 운영하게 되었을 때, 공통된 부분을 모듈화하고, 이 의존성을 추가하여 공유할 수 있습니다.
각 모듈의 기능을 파악하기 쉬워진다.
공통의 기능은 의존성 주입으로, 모듈별로 기능을 분리하여 작성하기 때문에, 코드의 이해가 쉬워집니다. 또한 해당 도메인의 로직만 남기 때문에 더 깔끔한 코드의 작성이 가능합니다.
빌드를 쉽게 진행할 수 있다.
./gradlew :moduleName:build
의 명령어를 통해 빌드를 쉽게 진행할 수 있습니다. 멀티프로젝트의 경우에는 별도로 빌드를 진행하는 반면에 멀티 모듈은 루트 프로젝트에서 각각의 모듈을 빌드할 수 있습니다.
멀티모듈을 진행하면서 제일 중요하다고 생각한 부분이 어떠한 모듈에 무엇을 위치 시킬 것 인가였습니다.
그 중에서도 특히 common-module에는 무엇을 넣을 것이고 어떻게 관리해야 할 것인가는 중요하게 고민해 볼 문제였습니다..
common 모듈은 말 그대로 공통으로 사용되기 때문에, 자칫 잘못 설계했다가는 과도한 의존성 덩어리가 되거나 특정 몇몇 서비스만을 위한 코드들이 들어가게 됩니다. 때문에 어느 정도의 중복을 기준으로 common module에 넣을 것이고, 어디까지의 역할만 시킬 것인지를 확실하게 나눠야합니다.
단일 서비스의 경우에도 api
/ domain
/ core
( + batch
...) 나 web
/ admin
등으로 분리할 수 있습니다.
MSA의 경우에는 각각의 서비스와 공통 모듈로 분리하면 운영이 편리합니다.
file > Project Settings > Modules > New Module
로 모듈을 생성합니다.
기존 프로젝트를 모듈로 변경할 때는 아래와 같이 진행합니다.
file > Project Settings > Modules > Import Module > 폴더 선택
으로 모듈로 생성해준 후, build.gradle
을 제외하고 빌드 관련 파일들은 삭제합니다.
common 모듈은 jar 파일로 생성되고 다른 프로젝트에 첨부되는 것이고, 실행 가능한 bootJar로 패키징 할 필요가 없기 때문에 build.gradle
에 아래 옵션을 추가해줍니다.
bootJar {
enabled = false
}
jar {
enabled = true
}
루트 프로젝트를 생성합니다.
루트 프로젝트에는 src
폴더가 필요 없으니 삭제해도 무방합니다.
혹은 기존 폴더 구조에 wrapper가 포함된 gradle/
폴더,build.gradle
, gradlew
, gradlew.bat
, setting.gradle
을 추가하여 프로젝트화시켜줍니다.
그리고 setting.gradle
에 include("module-name")
으로 하위 프로젝트를 추가합니다.
`
rootProject.name = 'authentication'
include('common-module')
include('gateway', 'eureka', 'config')
include('auth', 'user', 'post', 'feed', 'search')
루트 build.gradle
에 빌드 설정 및 의존성 정보를 추가합니다.
아래 옵션들을 통해 설정 적용 범위를 선택할 수 있습니다.
allproject | subproject | project(:projectname) |
---|---|---|
루트 프로젝트 포함 | 루트 프로젝트 제외 | 해당 프로젝트만 |
buildscript {
repositories {
mavenCentral()
}
}
plugins {
id 'org.springframework.boot' version '2.5.8'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
allprojects {
repositories {
mavenCentral()
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.sgs.authentication'
version = '0.0.1'
sourceCompatibility = '11'
ext {
set('springCloudVersion', "2020.0.5")
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.springfox:springfox-boot-starter:3.0.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'org.glassfish.jaxb:jaxb-runtime'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
test {
useJUnitPlatform()
}
}
루트 프로젝트의 build.gradle
에서 대부분의 빌드 설정을 관리하기 때문에, 서브 프로젝트에서는 해당 프로젝트를 위한 의존성 등 필요한 부분만 남겨 관리할 수 있게 됩니다.
또한 common 모듈을 사용하는 프로젝트라면 implementation project(':common-module')
을 추가해 사용할 수 있습니다.
모듈간 의존 방법에는
implementation
과api
가 있습니다.
A 에서 B를 사용하고, B에서 C를 사용하는 A>B>C 구조로 의존관계가 있을 때, C에서 변경이 일어난다면
implementation
은 B만 재빌드하고
api
은 A와 B 전부를 재빌드 합니다.
루트에서 아래 명령어를 입력하면 해당 모듈에 빌드파일이 생성됩니다!
./gradlew {module-name}:bootJar
멀티모듈 설계 이야기 with Spring, Gradle
Gradle 멀티 프로젝트 관리
(Gradle dependency) api와 implementation 차이
Gradle의 root gradle file에서 멀티 프로젝트 관리. subprojects를 이용한 코드 공용화 작업.
질문이 있습니다. 멀티 모듈로 MSA를 운영할 경우 배포가 문제가 될 것 같은데요
변경점이 있는 모듈만 빌드되고 배포되면 좋을 것 같은데 이러한 프로세스도 고려하셨나요??