SpringBoot 단일 모듈에서 소스 공통화를 위해 멀티모듈로 변경
- Project
- 2025. 4. 10.
회사에서 기존 B2B 소스를 기반으로 B2C 개발을 진행하기로 했다. B2B에서 쓰던 공통 소스들이 분명 있을거고, 또 B2B에서 적용될거와 B2C에 적용될게 동시에 있을게 분명하였다. 공통 부분을 관리해서 B2C와 B2B에 같이 적용시키기 위해서 멀티모듈을 하자고 제안했다.
우선 B2B 소스를 멀티모듈로 나눠야 했었는데 Application, Service, Repository 레이어드 아키텍처 기반으로 분리를 하였다.

그리고 실제 작업의 모듈 이름은 아래와 같이 common, b2b-api, common-service, common-domain 으로 분리를 하였다.
setting.gradle.kts 파일에서 include 안에 모듈명을 인입하여 모듈을 생성하면 된다.


구성 설명
goyoai-backend-system
├── goyoai-web-common/
├── goyoai-web-common-domain
├── goyoai-web-common-service
├── goyoai-web-b2b
├── goyoai-web-b2c
b2b를 레이어드 아키텍쳐에 따라 application, service, domain 계층으로 나누었다.
application → service → domain 모듈로 나누고 경계를 구분하였다.
- application계층 : controller
- service계층: dto, service (비즈니스 로직)
- domain계층: entity, dao, enumeration (데이터 구조 정의)
goyoai-web-common
- 모든 모듈에서 사용될 공통 소스 어떠한 의존성도 맺으면 안된다.
goyoai-web-common-domain
- 공통으로 사용될 데이터관리 소스(entity, dao, enumeration)만 있다.
- goyoai-web-common 만 의존한다.
goyoai-web-common-service
- 공통으로 사용될 서비스 소스(service) 비즈니스 로직이다.
- goyoai-web-common, goyoai-web-common-domain 만 의존하여야 한다.
goyoai-web-b2b
- b2b의 controller와 공통에서 사용되지 않고 b2b용으로 커스텀돼서 사용될 소스이다.
- 위에서 만든 common쪽 모두 의존한다.
goyoai-web-b2b
- b2c의 controller와 공통에서 사용되지 않고 b2c용으로 커스텀돼서 사용될 소스이다.
- 위에서 만든 common쪽 모두 의존한다.
Common쪽에서 관리되는 파일에서 b2b, b2c로 나뉘어졌을때
앞의 접두사로 B2B, B2C 기입한다.
ex) B2BUserService, B2CUserService
build.gradle.kts 세팅
루트 모듈에서의 build.gradle.kts
루트모듈에서는 allprojects와, subprojects를 설정 해줘야한다.
allprojects {} : (루트 + 하위) 모듈 plugin 및 공통 의존성 세팅
subprojects {} : 하위 모듈의 의존성 세팅
allporjedcts 설정에는 프로젝트에 대한 설명과 사용할 plugin들 설정 그리고 tasks 설정이다.
application 계층의 모듈 빼고는 bootWar나 bootJar가 필요없고 jar만 있으면 되기 때문에 jar만 생성되도록 만들었다.
allprojects {
description = "GoyoAi Web Service"
group = "io.goyoai.web"
version = "2.0.0"
apply(plugin = "java")
apply(plugin = "kotlin")
apply(plugin = "kotlin-spring")
apply(plugin = "kotlin-jpa")
apply(plugin = "kotlin-kapt")
dependencyManagement {
val springCloudVersion = "2022.0.4"
imports {
mavenBom("org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}")
}
}
allOpen {
annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.MappedSuperclass")
annotation("jakarta.persistence.Embeddable")
}
repositories {
mavenCentral()
maven { url = uri("https://repo.spring.io/milestone") }
maven { url = uri("https://repo.spring.io/snapshot") }
}
// 라이브러리용 jar만 생성
tasks.getByName<Jar>("jar") {
enabled = true
}
tasks.getByName<War>("war") {
enabled = false
}
tasks.bootJar {
enabled = false
}
tasks.bootWar {
enabled = false
}
tasks.test {
enabled = false
}
}
// 하위 모듈만 설정
subprojects {
dependencies {
// starter
implementation("org.springframework.boot:spring-boot-starter-data-jpa:3.1.6")
implementation("org.springframework.boot:spring-boot-starter-validation:3.1.6")
implementation("org.springframework.boot:spring-boot-starter-web:3.2.0")
implementation("org.springframework.boot:spring-boot-starter-mail:3.1.6")
implementation("org.springframework.boot:spring-boot-starter-thymeleaf:3.1.6")
implementation("org.springframework.boot:spring-boot-starter-data-redis:3.1.6")
implementation("org.springframework.session:spring-session-data-redis:3.1.6")
implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.0.4")
implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5")
}
하위 모듈에서의 build.gradle.kts
1. application 계층에 서는 common과, common-service를 의존성 맺고 배포를 해야되기 때문에 bootWar를 만들어야 한다.
그리고 swagger같은 application 계층에서만 필요한 의존성들을 넣고 설정한다.

domain 계층에서는 common인 공통 모듈만 의존하고, service에서는 common과 도메인계층만 의존한다.


멀티 모듈 이유
1. 의존성 방향을 깔끔하게 유지하기 위해
- application → service → domain 방향으로 의존을 맺기 때문에 서로 의존하지 못하게 강제할수있음 유지보수 확장성을 증대한다.
- 예를들어서 사용자에게 보여줄 dto를 domain계층에서 사용되고 있다면 이것은 domain계층이 application 계층에 의존하고 있다 이것을 강제로 분리하게 해서 의존을 못맺게 하니 명확하게 구분지어줄수있다.
- 공통 소스를 common 으로 분리해서 b2b와 b2c 동시 적용 가능하다(재사용성, 공통화 가능)
- domain, service 를 나눔으로써 다른 서비스가 모듈로 들어왔을때 domain만 의존해서 사용가능 하다.
고민
아래 우아한 기술블로그에서도 보면은 우리와 동일하게 service에서 사용자에게 보여줄 dto를 만들고 요청데이터 그대로 service에서 받고 있었는데 이러면 service가 controller에 종속적이게 된다고해서 안좋다고 한다.

위 사례로 따지면 레이어드 아키텍쳐를 구분했을때 원래는 controller에서 service로 요청보내는 request dto와 service에서 controller로 보내는 response dto를 따로 만들어줘야 한다. 그래도 dto가 service에서 있고 application 계층에도 따로 있어야 하지만
지금은 service에서 모두 dto를 관리해서 service에서만 사용된다. dto를 service 계층에 맞게 다 따로 만들어줘야 되는데 너무 시간이 오래걸리고 앞으로도 그렇게 하자니 개발하는데 너무 힘들것같다. 우선은 dto를 service 계층에 넣었지만 지금당장은 바꾸기 힘들것같다. 나중에 꼭 필요하다고 생각될 이유가 있을때 바꿔야 할것같다.