앞선 글에서는 멀티모듈과 MSA란 무엇인지 이론적인 부분을 중점적으로 살펴보았는데요, 직접 멀티모듈을 적용해서 프로젝트 구조를 구성해보겠습니다.
1️⃣ 프로젝트 환경
- Spring Boot 2.7
- Gradle
- Java 11
- JPA
- Rest API
2️⃣ 모듈 구성
레이어드 아키텍처 기반으로 멀티 모듈을 구성했습니다.
- common
- api-diary
- application
- auth
- aws
- core-mysql
- core-redis
구성 시에 jpa 특성상 domain과 infra를 완전히 나누는게 큰 의미가 없다고 생각되어서, domain-infra를 묶어 하나의 모듈로 생성하였습니다.
1. api-diary
presentation 레이어 모듈로서, 전체 다이어리 서비스의 API 요청과 응답을 처리하는 역할입니다.
- common
- API 컨트롤러 내에서 공통으로 사용하는 paging request가 있습니다.
- config
- 자동 API 문서화를 지원하는 Swag
- ger를 사용하고 있기 때문에 관련 설정을 해당 모듈에 넣어주었습니다.
- diary…
- api 요청과 응답을 처리하는 컨트롤러가 포함됩니다. diary외에도 member, friend, comment 등이 도메인별로 나뉘어져있습니다.
- exception
- API 요청 후 Application → Domain 로직에서 생긴 에러를 Controller로 넘겨서 응답하는 ApiExceptionAdvice가 있습니다. 서로 다룬 모듈에서 쓰이는 에러 처리는 다른 포스팅에서 다루었습니다.
- Application main 클래스
- @SpringBootApplication이 달린 main 클래스가 이 모듈에 존재합니다.
2. application
비즈니스 로직을 담당하는 모듈입니다.
- common
- paging request를 받아서 domain계층에서 쓰일 Pageable로 변환하는 pagingDto가 있습니다.
- diary…
- api 요청/응답에 대한 비즈니스 처리를 담당합니다.
- exception
- 비즈니스 계층에서 사용할 exception들이 정의되어 있습니다.
3. auth
이 auth 모듈에서는 presentation 레이어를 다루지 않습니다. api모듈에서 요청을 다 받고 반환까지 다루기 때문이죠.
그래서 auth와 관련된 로직만을 다룰 수 있도록 분리하였습니다.
- security
- jwt 관련 클래스들을 security에 묶은 것은, jwt 토큰의 작동 방식이 security에 의존적이라고 생각했기 때문입니다.
- security filter chian을 기반으로 토큰을 주고받으며 이를 기반으로 authentication 과정을 마칩니다.
4. aws
외부 api를 사용하는 모듈은 따로 구성하였습니다.
저의 경우 aws에서는 s3만을 사용하고 있습니다.
5. core-mysql / core-redis
현재 프로젝트에서 사용하는 infra는 mysql와 redis로 2개입니다. 한 모듈에 하나의 책임을 갖게 하기 위해서 인프라별로 모듈을 분리했습니다.
두 모듈 다 jpa를 사용하고 있고, mysql의 경우 프로젝트의 루트로 사용하고 있습니다.
redis는 jwt refresh token을 담아두기 위한 저장소로 쓰이고 있습니다.
간단한 데이터만을 저장하기 때문에 db의 효율성을 위해서도 좋고, 데이터를 생성 할 때 지정한 만료시간이 지나면 자동으로 삭제되기 때문에 따로 batch작업을 하지 않아도 되어 편리하기때문에 사용하고 있습니다.
6. common
3️⃣ 의존성 관리
모듈이 여러개라는 뜻은, 여러개의 프로젝트로 나뉘었던 것을 한 프로젝트에 여러 모듈로 나누었다는 의미입니다.
이전에 여러 프로젝트에서 스파게티처럼 사용했던 의존성을 역할과 책임에 따라 분리하면, 각 모듈의 정체성을 한번에 파악할 수 있으며 유지보수가 쉬워집니다.
또한 gradle을 직접 빌드할 때, 의존성 관리를 잘 해야 각 모듈에 필요한 의존성이 포함되어 jar 빌드를 할 수 있습니다.
루트 프로젝트
build.gradle
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.17'
id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
subprojects {
group = 'com.mmd'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
repositories {
mavenCentral()
}
dependencies {
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}
}
// 모든 모듈은 common 모듈을 의존한다.
configure(subprojects.findAll {it.name != 'common'}) {
dependencies {
implementation project(':common')
// test
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
test {
useJUnitPlatform()
}
}
공통으로 사용되는 의존성이나 설정을 기입해줍니다. (예: test, common)
setting.gradle
rootProject.name = 'mmd'
include 'common'
include 'api-diary'
include 'application'
include 'auth'
include 'aws'
include 'core-mysql'
include 'core-redis'
하위 모듈에 대한 설정을 입력합니다.
api-diary
dependencies {
**implementation project(':application')
implementation project(':auth')
implementation project(':aws')**
// spring web
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
// spring jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// spring security
implementation 'org.springframework.boot:spring-boot-starter-security'
// Swagger
implementation group: 'io.springfox', name: 'springfox-boot-starter', version: '3.0.0'
implementation group: 'io.springfox', name: 'springfox-swagger-ui', version: '3.0.0'
}
bootJar { // 다른 dependency와 함께 build해야함
enabled = true
}
jar {
enabled = false
}
- application과 auth, aws를 의존합니다.
- security는 Controller에서 사용자 정보를 바인딩하기 위해 필요합니다. jpa는페이징을 위해 의존성을 추가했는데, 직접 request를 구현해서 사용하는 것이 더 바람직해보입니다.
- api모듈은 domain모듈을 의존하지 않음으로써 도메인에 어떤 로직이 있는지 알 필요가 없게 됩니다.
application
dependencies {
**implementation project(':core-mysql')
implementation project(':core-redis')**
// spring web
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly "jakarta.transaction:jakarta.transaction-api"
// spring jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-test-autoconfigure'
}
bootJar {
enabled = false
}
jar {
enabled = true
}
application 모듈에서는 서비스의 비즈니스 로직을 다루기 때문에, 비즈니스에 필요한 도메인만을 의존합니다.
api계층을 의존하지 않아 요청/응답 방법에 상관없이 비즈니스 로직이 작동합니다.
aws
dependencies {
// amazon s3
implementation "org.springframework.cloud:spring-cloud-starter-aws:2.0.1.RELEASE"
implementation 'org.springframework.boot:spring-boot-starter-web'
}
bootJar {
enabled = false
}
jar {
enabled = true
}
aws는 외부 api이므로, common 모듈만을 의존합니다.
core-mysql
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
plugins{
id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10'
}
dependencies {
// mysql
runtimeOnly 'com.mysql:mysql-connector-j'
// jpa
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// querydsl
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
implementation 'org.springframework:spring-web'
}
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
compileQuerydsl{
options.annotationProcessorPath = configurations.querydsl
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
querydsl.extendsFrom compileClasspath
}
bootJar {
enabled = false
}
jar {
enabled = true
}
도메인 모듈 중 하나인 mysql 모듈입니다.
Mysql, jpa와 queryDSL에 대한 의존성을 갖고있습니다.
core-redis
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
bootJar {
enabled = false
}
jar {
enabled = true
}
redis 모듈에서는 spring data redis 라이브러리를 사용하므로 이에 대한 의존성을 추가해줍니다.
✔️ 마무리
이렇게 프로젝트의 구성과 설정은 모두 끝이 났습니다.
현재 모듈에서는 외부 api나 batch에 대한 고민이 부족한 점, 그리고 MSA로 발전하기 위해서는 도메인 기반으로 모듈을 구성하는것이 좋다는 점을 다시 고려해볼 수 있습니다.
'프로젝트 Project' 카테고리의 다른 글
static 내부 클래스로 DTO 관리하기 (0) | 2024.08.06 |
---|---|
Spring Security + JWT로 로그인 구현하기 (1) (0) | 2024.08.05 |
Ehcache로 캐싱하기 (0) | 2024.08.02 |
쿼리 튜닝하기 (0) | 2024.08.02 |
인덱스 적용하기 (0) | 2024.08.02 |