현재 진행하고 있는 프로젝트의 멀티 모듈화를 마치고 해당 글을 작성하여 글의 내용에 누락이 있을 수 있다는 점에 대해 양해를 부탁드립니다.
프로젝트 설명
먼저 프로젝트에 관해 간단하게 설명하자면 8to10 이라는 이름의 갓생을 살고자하는 P 성향의 사람에게 세우기 귀찮은 계획을 세워주며 일정 수행을 어느정도 강제하기 위해 진행한 일정관리 프로젝트이다.
프로젝트를 진행할 때 기업이 운영하는 정도의 대규모 어플리케이션 정도로 설계하는 것은 불가능 하겠지만 사용자가 많은 어플리케이션의 상황을 어느정도 가정하고 설계하려고 했다. 그렇기 때문에 설계적 관점과 유지보수 측면에서의 관점 둘다에 대해서 고려할 필요가 있었다.
아래는 해당 프로젝트의 아키텍처 자료이다.
요약하자면 1개의 클러스터 3개의 pod로 AutoScaling되는 분산서버, 단일 db, 단일 캐시로 구성된 아키텍처이다.
멀티 모듈화의 이유
- Scheduling 서버의 분리
서비스의 기능 중 정해진 시간에 사용자에게 알람으로 하나의 피드백 메시지를 날려주는 스케쥴링 서비스를 필요로 한다.
하지만 위 구조에서 3개의 서버가 중복된 피드백 메시지를 동일 사용자에게 전송하는 문제가 있었다. 그렇기 때문에 Scheduling 서버에 대한 분리가 필요했다. - 멀티 모듈화로 얻을 수 있는 추가적인 이점들
- 테스트 코드 구현시 테스트 환경의 분리
- 변경된 모듈만 개별적으로 빌드 및 배포 가능
- 등등
멀티 모듈화를 생각하게 되었던 가장 큰 이유는 Scheduling 서버의 분리 때문이었다. Scheduling 서버의 분리 이외에도 멀티 모듈화를 진행하면서 얻을 수 있는 부수적인 장점도 많았다. 모듈화를 진행하는 과정에서 여러가지에 대해 고민을 하면서 단순히 기존의 코드를 분리하는 것이 아니라 도메인객체와 엔티티 객체를 분리하고 도메인 객체를 바라보는 관점과 레이어를 바라보는 관점 그리고 테스트 코드를 다시 작성하고 기존의 코드를 수정하는 과정에서 많은 부분을 배울 수 있었다. 그렇기에 나의 고민과 진행 과정을 시리즈 글로 남기려고 하고 이 글을 보는 사람들과 다양한 생각을 나눌수 있으면 좋을 것 같다.
이번 글에서는 내가 했던 gradle 세팅에 대해 작성하고자 한다.
Gradle 세팅 및 모듈 추가
기존 gradle 구성
build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// spring security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.mysql:mysql-connector-j'
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
//json
implementation 'org.json:json:20231013'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// P6Spy 의존성 추가 (개발 서버용)
implementation 'p6spy:p6spy:3.9.1'
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.h2database:h2'
testImplementation 'com.github.codemonstur:embedded-redis:1.4.3'
//parametererized test
testImplementation 'org.junit.jupiter:junit-jupiter-params'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
위 gradle 세팅을 보면 여러 역할을 하는 implementation 구문들이 쌓여있는 것을 확인할 수 있다. 이는 해당 모듈이 정확히 어떤일을 하고 있는지 파악하기 힘들게 만들고, 하나의 기능에 대한 변경이 다른 코드에 대해 영향을 미칠 수 있다는 것을 뜻한다.
settings.gradle
rootProject.name = 'eighttoten'
패키지 구조
기존의 패키지 구조는 다음과 같다.
현재 패키지의 구조를 보면 achivement라는 도메인 관련 패키지 하위에 모든 레이어의 코드들이 혼재되어 있는 상태이지만 data access와 관련된 코드들은 data access 와 관련된 모듈안에 모여있는 것이, 다른 계층도 마찬가지로 관련된 기능들이 같은 모듈안에 모여있는 것이 더 응집도 측면에서 잘 응집되어 있는 것이 아닐까 라는 생각이 든다. 이 기능들을 관심사에 맞게 차근차근 하나씩 분리하고자 한다.
아래는 이를 위해 공통적으로 해야할 gradle 세팅들에한 내용이다.
1. 기존의 settings.gradle 상위 폴더로 빼주고 다음과 같이 변경했다.
`rootProject.name = 'eighttoten'`
include(
"8to10_backend"
)
위 설정을 통해 프로젝트에서 8to10_backend 모듈을 include 할 수 있게 된다.
2. 기존의 build.gradle 파일을 상위 폴더에 복사 붙여넣기 한다.
해당 파일을 상위에 생성하는 이유는 공통 implementation이나 프로젝트 세팅을 위해서 이다. 아직은 하나의 모듈만 있기때문에 복사 붙여넣기 했다.
3. 모듈들의 공통 버전 관리를 위해 gradle.properties 파일을 상위 폴더에 생성한다.
이렇게 gradle.properties 파일을 생성함으로써 각 버전들에 대한 변수를 정의하고 하나의 파일에서 모듈들의 버전에 대해 통합적으로 관리할 수 있다.
### Application Version ###
applicationVersion=0.0.1
### Project configs ###
projectGroup=online.8to10
javaVersion=17
springBootVersion=3.2.5
springDependencyManagementVersion=1.1.4
4. settings.gradle 수정
settings.gradle 에서 서브 모듈들의 플러그인 버전을 관리 한다. 이렇게 중앙에서 플러그인을 관리함으로써 모듈들의 버전 충돌을 방지하고 유지보수성을 늘릴 수 있다.
pluginManagement {
String springBootVersion = settings.getProperty("springBootVersion")
String springDependencyManagementVersion = settings.getProperty("springDependencyManagementVersion")
resolutionStrategy {
eachPlugin {
if (requested.id.id == "org.springframework.boot") {
useVersion(springBootVersion)
} else if (requested.id.id == "io.spring.dependency-management") {
useVersion(springDependencyManagementVersion)
}
}
}
}
5. 상위 폴더(아까 복사 붙여넣기 했던) build.gradle 수정
plugins {
id 'java'
id 'org.springframework.boot' apply false
id 'io.spring.dependency-management' apply false
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
allprojects {
java.sourceCompatibility = javaVersion
group = projectGroup
version = applicationVersion
repositories {
mavenCentral()
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// spring security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'com.mysql:mysql-connector-j'
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
//json
implementation 'org.json:json:20231013'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
// P6Spy 의존성 추가 (개발 서버용)
implementation 'p6spy:p6spy:3.9.1'
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'com.h2database:h2'
testImplementation 'com.github.codemonstur:embedded-redis:1.4.3'
//parametererized test
testImplementation 'org.junit.jupiter:junit-jupiter-params'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
def generated = 'src/main/generated'
// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
options.getGeneratedSourceOutputDirectory().set(file(generated))
}
// java source set 에 querydsl QClass 위치 추가
sourceSets {
main.java.srcDirs += [ generated ]
}
// gradle clean 시에 QClass 디렉토리 삭제
clean {
delete file(generated)
}
tasks.named('test') {
useJUnitPlatform()
}
tasks.getByName("bootJar"){
enabled = false
}
tasks.getByName("jar"){
enabled = true
}
}
위와 같이 apply 를 적용함으로 써 특정 서브 모듈에서 필요한 플러그인은 해당 서브 모듈에서 따로 정의하여 적용할 수 있다. 현재는 모듈이 하나밖에 없기 때문에 기존의 플러그인을 그대로 submodule에 적용해놓은 상태이다. 최종적으로 build.gradle 파일에서 dependency 관련 설정 빼고는 버전에 관련된 모든 숫자가 제거된 것을 확인할 수 있다.
'Spring > 개인 프로젝트' 카테고리의 다른 글
[성능 최적화] 일정조회 성능개선 (Feat. 인덱스) (0) | 2025.03.08 |
---|---|
SSE + Redis Pub/Sub, Scale-out을 고려한 알람 시스템 구현 (0) | 2024.12.19 |
로그인 성능 개선 (Feat. Redis) (0) | 2024.12.02 |
개발 서버 더미 데이터 추가 (1) | 2024.12.01 |