때는.. 바야흐로 일주일 전... 디프만 OT..
👥 우리 서버팀 : 웅성👥 웅성 👥 웅ㅅ멀티 웅성성 모듈 웅성👥
🐰 주디박 : 뭔데?!! 멀티??? 모??? 뭐라고요?? 멀티 모듈??? 그게 모지!!? DDD????
👥 우리 서버팀 : 한 프로젝트에 여러 모듈 개발하는 방법이 있어요!!
🐰 주디박 : 예??? 아~~~~ 그렇구나~~~ (모르겠음)
우리 서버팀에서 도전하고 싶어 하고 궁금해하는 멀티 모듈!!! 그게 뭘까?
팀에 도입하고 싶은데 우선 나는 궁금해졌다. 그게 뭔데? 그게 왜 필요한데???
그래서 공부를 시작했다. 두둥 탁
1. 멀티 모듈 프로젝트
✔️ 모듈
Oracle Java 문서에서 모듈이란 패키지의 한 단계 위의 집합체이며, 관련된 패키지와 리소스들의 재사용할 수 있는 그룹이라고 정의하고 있다.
이때 각 모듈은 독립적으로 개발, 빌드, 테스트, 배포가 가능하다.
✔️ 멀티 모듈
멀티 모듈 프로젝트는 상호 연결된 여러 개의 모듈로 구성된 프로젝트를 의미한다. 즉, 멀티 모듈 단일 프로젝트.
조금 더 자세하게 예시로 살펴보자.
✔️ (예시) 회원 시스템
- 단일 모듈 멀티 프로젝트 : 각각 별도의 프로젝트로 만들고 많은 부분의 코드를 복사+붙여넣기를 통해 구현
- 멀티 모듈 단일 프로젝트 : Java 빌드 도구의 멀티 모듈을 사용하면, 공통된 구현(common)을 공유하는 방식으로 코드를 작성
천천히 살펴보자.
회원 시스템에는 다음과 같은 독립적인 프로젝트 단위가 있다.
- member internal api : 내부에 관리자 페이지에서 호출하는 api 서버
- member external api : 고객 화면에서 호출하는 api 서버
- member batch : 주기적인 작업을 수행하는 서버
[ 단일 모듈 멀티 프로젝트 ]
3가지 프로젝트 모두 유저라는 동일한 도메인에 대해서 구성하고 있기 때문에 중복 코드가 발생한다. (ex. Member 도메인 모델)
그런데 만약에 이러한 중복 코드에 수정사항이 생긴다면.. 개발자는 눈물을 흘릴 것이다. 유지보수 난이도 극상.
[ 멀티 모듈 단일 프로젝트 ]
그래서 기존의 단일 프로젝트 내에 여러 개의 모듈을 가질 수 있는 구조가 나왔다.
위 그림처럼 하나의 프로젝트 내에서 기존의 프로젝트 단위는 모듈이 되었고, 공통된 코드(common)는 분리해서 함께 사용할 수 있다.
✔️ 멀티 모듈? DDD? MSA?
좋은 아키텍처는 시스템이 모노리틱 구조로 태어나서 단일 파일로 배포되더라도, 이후에는 독립적으로 배포 가능한 단위들의 집합으로 성장하고, 또 독립적인 서비스나 마이크로서비스 수준까지 성장할 수 있도록 만들어져야 한다.
또한 좋은 아키텍처라면 나중에 상황이 바뀌었을 때 이 진행 방향을 거꾸로 돌려 원래 형태인 모노리틱 구조로 되돌릴 수도 있어야 한다.
- 클린 아키텍처
처음에 팀원에게 설명을 들을 때 떠오른 개념은 DDD와 MSA이다. 이 두 가지도 제대로 알지 못했기 때문에 개념이 전부 비슷하게 느껴졌다.
DDD(Domain-driven Design), MSA(Microservice Architecture), 멀티 모듈은 서로 다른 개념이지만, 연관성이 있다!
[ DDD(Domain-driven Design) ]
DDD는 소프트웨어 개발 방법론 중 하나로, 도메인 모델을 중심으로 개발을 진행하는 방법이다.
도메인 모델 중심으로 개발을 진행하기 때문에, 각 도메인 모델은 서로 분리되어 있어야 한다.
[ 멀티 모듈 ]
멀티 모듈 프로젝트를 활용하여 DDD를 적용하면, 각 도메인 모델은 각각의 모듈로 구분되어 있으므로, 도메인 모델 간의 결합도를 낮출 수 있다. 이를 통해 코드의 복잡도를 낮추고, 유지보수성을 높일 수 있다.
[ MSA(Microservice Architecture) ]
이렇게 도메인 모델 중심으로 분리된 멀티 모듈 프로젝트는 모듈이 MSA로 발전될 수 있다.
즉, 멀티 모듈 프로젝트는 MSA를 구현하는 데 사용될 수 있다.
2. 왜 멀티 모듈을 사용하는가?
1. 코드의 중복을 줄일 수 있다.
공통된 로직이 있는 여러 서비스를 운영하게 되었을 때, 공통된 부분을 모듈화 하고, 이 의존성을 추가하여 공유할 수 있다.
2. 각 모듈의 기능을 파악하기 쉬워진다.
공통의 기능은 의존성 주입으로, 모듈별로 기능을 분리하여 작성하기 때문에, 코드의 이해가 쉬워진다.
3. 빌드를 쉽게 진행할 수 있다.
./gradlew :moduleName:build
위 명령어를 통해 빌드를 쉽게 진행할 수 있다. 멀티프로젝트의 경우에는 별도로 빌드를 진행하는 반면에 루트 프로젝트에서 각각의 모듈을 빌드할 수 있다.
3. 멀티 모듈 적용하기
나는 springboot와 gradle로 멀티 모듈 프로젝트를 적용해 보겠다.
아직 Best Practice를 찾아나가는 중이다!!! 아직 따라 하지 마세요(?)🤖 : 다 완성하고 올려야지 왜 지금 올렸니?🐰주디박: 어음.. 개발 과정...? 성장 과정...? 문서 유지보수 중..? (아무 말 다 가져오기)다행히 우리 디프만에는.. Best Practice가 많은 듯 후후후.. 스터디만 끝나고 업데이트한다 진짜.. (4월 18일 전까지 업로드 약속..)
1) Gradle 프로젝트 생성
이때 하나의 프로젝트에서 여러 모듈을 관리할 거다!!
공통 코드는 Common 모듈에서 관리하기 때문에 루트 프로젝트의 src 폴더는 삭제한다.
2) 모듈 추가
일단 연습이기 때문에 3가지 모듈을 생성했다.
Root Project (setting.gradle)
하위 프로젝트에 대한 setting.gradle을 인텔리제이에서 자동으로 넣어준다. 똑똑하네 이 친구. 🤖
만약 코드가 없다면 include #{모듈명}으로 추가할 수 있다!
Root Project (build.gradle)
: 레거시 버전
더 자세하게 알아보던 중 팀원이 공유해준 블로그에서 해당 코드가 레거시 코드임을 알게 되었다. 다른 버전으로 다시 작성해보자!
buildscript {
ext {
springBootVersion = '2.7.10'
}
repositories {
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath "io.spring.gradle:dependency-management-plugin:1.0.15.RELEASE"
}
}
// 하위 모든 프로젝트 공통 세팅
subprojects {
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group 'org.example'
version '1.0-SNAPSHOT'
sourceCompatibility = '11'
targetCompatibility = '11'
compileJava.options.encoding = 'UTF-8'
repositories {
mavenCentral()
}
// 하위 모듈에서 공통으로 사용하는 세팅 추가
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}
}
project(':Practice-Common') {
// 공통 코드
bootJar { enabled = false } // core 은 bootJar 로 패키징 할 필요 없음
jar { enabled = true }
dependencies {
}
}
project(':Practice-Api') {
bootJar { enabled = true }
jar { enabled = false }
dependencies {
implementation project(':Practice-Common') // 컴파일 시 Practice-Common project 로딩 (의존성 주입)
implementation 'org.springframework.boot:spring-boot-starter-web'
}
}
project(':Practice-Batch') {
bootJar { enabled = true }
jar { enabled = false }
dependencies {
implementation project(':Practice-Common') // 컴파일 시 Practice-Common project 로딩 (의존성 주입)
}
}
* project(:projectPath)
다음 방식으로 정의한 모듈의 의존성을 추가하여 사용할 수 있다.
위 프로젝트에서는 Practice-Common가 공통 모듈이라 의존성을 추가해 줬다.
* 이때 Practice-Common만 bootJar가 false로 설정되어 있다.
그 이유는 기본적으로 gradle build시 실행 가능한 jar 파일을 만드는데, Common은 이를 자체적으로 실행을 시키지 않기 때문에 다음과 같은 설정을 추가한 것이다.
설정 후 의존 관계가 주입되었는지 확인 가능하다.
: 새로운 버전
plugins {
id 'org.springframework.boot' version '2.7.10'
id 'java'
}
repositories {
mavenCentral()
}
bootJar.enabled = false
subprojects {
group = 'practice.multimodule'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
apply plugin: 'java'
// build.gradle에서 api() 를 사용하려면 java-library 사용
apply plugin: 'java-library'
apply plugin: 'org.springframework.boot'
// spring boot dependency를 사용하여 사용중인 부트 버전에서 자동으로 의존성을 가져온다.
apply plugin: 'io.spring.dependency-management'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
// 관리하는 모듈에 공통 dependencies
dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 'org.projectlombok:lombok:1.18.12' // 테스트 의존성 추가
testAnnotationProcessor 'org.projectlombok:lombok:1.18.12' // 테스트 의존성 추가
}
test {
useJUnitPlatform()
}
}
Api 모듈 설정
- build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
- resource > application.yml
server:
port: 8000
Batch 모듈 설정
- build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
- resource > application.yml
server:
port: 8001
각각 서버 띄우기 성공!
아... 세팅이 반이네
홀란스럽다..(?)
📌 Reference
'프로젝트 개발 기록 > [개발] java | spring' 카테고리의 다른 글
코딩 컨벤션 설정 | Spotless, Checkstyle ⚙️✨ (1) | 2023.05.09 |
---|---|
[Java] Mutable, Immutable, 방어적 복사, unmodifiableList (0) | 2023.05.02 |
[Java] 람다와 스트림 (Lambda & Stream) (0) | 2022.11.13 |
[Java] 코딩테스트 대비 정리 (2) (0) | 2022.11.07 |
댓글