Gradl 멀티 모듈 구성하기

Daniel·2025년 6월 22일
0

Back-End

목록 보기
55/56

들어가며

PostgreSQL 와 Redis 를 동시에 학습하고 싶어서 멀티 모듈을 구성해보았다.
처음엔 "폴더만 나누면 되겠지?"라고 생각했는데, 생각보다 훨씬 복잡하고 배울 게 많았다.

멀티 모듈이란?

자바에서 Module(모듈) 은 독립적으로 배포될 수 있는 코드의 단위를 말한다.
패키지의 한 단계 위의 집합체이며, 서로 밀접하게 연관된 패키지들과 리소스들의 그룹을 의미한다.
각 모듈은 독립적으로 개발, 빌드, 테스트, 배포 가 가능하다.

멀티모듈의 핵심 개념

  • 독립성: 각 모듈이 별도로 실행 가능
  • 재사용성: 다른 모듈에서 참조하여 활용 가능
  • 관리성: 기능별로 분리하여 유지보수 용이

왜 멀티 모듈을 선택했나?

PostgreSQL과 Redis를 각각 학습하고 싶었다. 하지만 따로따로 프로젝트를 만들기엔 뭔가 아쉬웠다.
실무에서는 PostgreSQL과 Redis를 연동한 패턴을 많이 사용한다고 들어서 나중에 통합을 생각했다.

목표: 하나의 프로젝트에서 둘 다 학습 및 실습 해보고 나중에 통합된 연동 패턴을 실습해보자

멀티 모듈 구성하기

처음엔 멀티 모듈을 단순하게 생각했다.

내가 처음 이해한 멀티 모듈:

  • 그냥 폴더를 여러 개로 나누는 것
  • 각 폴더에 다른 기술을 넣는 것
  • Gradle이 알아서 관리해줄 것

실제 멀티 모듈:

  • 각 모듈이 독립적인 Spring Boot 애플리케이션
  • 모듈 간 의존성 관리가 핵심
  • 공통 설정과 개별 설정을 분리해야 함

생각보다 훨씬 체계적이고 복잡했다.

첫 번째 시도: 단순한 접근

초기 접근

postgres-redis-practice/
├── postgres/     # PostgreSQL만 다루는 곳
├── redis/        # Redis만 다루는 곳  
└── integration/  # 둘 다 쓰는 곳

위처럼 폴더만 나누고, 각각에 Spring Boot를 적용시켰다.

문제 1: 실행이 안됨

./gradlew :postgres:bootRun
# Task ':postgres:bootRun' not found

뭔가 Gradle이 postgres를 모듈로 인식을 못하고 있었다.

문제 2: 중복 설정

// postgres/build.gradle
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

// redis/build.gradle  
implementation 'org.springframework.boot:spring-boot-starter-web'  // 중복!
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

똑같은 설정을 모든 모듈에 반복하고 있었다.

첫번째 시도: 문제가 왜 발생했을까?

문제1: settings.gradle 설정

// settings.gradle
rootProject.name = 'postgres-redis-practice'

include 'postgres'
include 'redis'  
include 'integration'

위 설정이 있어야 Gradle이 각 폴더를 모듈로 인식한다.

문제2: 각 모듈의 독립성

처음엔 각 모듈이 완전히 분리되어야 한다고 생각했다. 하지만 실제로는:

  • 독립적 실행: 각 모듈이 혼자서도 돌아가야 함
  • 의존성 활용: integration 모듈에서 다른 모듈을 참조할 수 있어야 함

두 번째 시도: 구조 개선

포트 분리

# postgres/application.yml
server:
  port: 8082

# redis/application.yml  
server:
  port: 8083

# integration/application.yml
server:
  port: 8080

각 모듈마다 포트를 분리 설정해 충돌을 막았다.

모듈별 의존성 관리

// 루트 build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.3' apply false
    id 'io.spring.dependency-management' version '1.1.7' apply false
}

// 모든 서브 프로젝트에 공통 적용 (Ex. Spring Boot)
subprojects {
    apply plugin: 'java'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'
    
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
    }
    
    repositories {
        mavenCentral()
    }
    
    // 공통 의존성
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        compileOnly 'org.projectlombok:lombok'
        developmentOnly 'org.springframework.boot:spring-boot-devtools'
        annotationProcessor 'org.projectlombok:lombok'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
}

// 각 모듈별 특화 의존성
project(':postgres') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-validation'
        runtimeOnly 'org.postgresql:postgresql'
    }
}

project(':redis') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-redis'
        implementation 'org.springframework.boot:spring-boot-starter-cache'
    }
}

project(':integration') {
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-data-redis'
        implementation 'org.springframework.boot:spring-boot-starter-cache'
        runtimeOnly 'org.postgresql:postgresql'
    }
}

모듈별 공통 의존성은 root build.gradledependencies{ } 안에 설정하고 추가&특화 의존성은 위 처럼 따로 설정해 주었다.

implementation project(':postgres')

다른 모듈에서 위 한줄로 postgres 모듈의 모든 Entity와 Repository를 사용할 수 있다.

세 번째 시도: Docker 환경 구성

학습 환경의 일관성을 위해 Docker Compose를 활용했다.

# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    container_name: postgres-practice
    environment:
      POSTGRES_DB: practice_db
      POSTGRES_USER: daniel
      POSTGRES_PASSWORD: password123
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    container_name: redis-practice
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes

volumes:
  postgres_data:

워크플로우

# 1. 환경 구축 (2초)
docker-compose up -d

# 2. 각 모듈 독립 실행 (학습 단계)
./gradlew :postgres:bootRun   # PostgreSQL 패턴 학습
./gradlew :redis:bootRun      # Redis 패턴 학습

# 3. 통합 실행 (응용 단계)  
./gradlew :integration:bootRun # 학습한 패턴들 통합 활용

# 4. 전체 빌드 및 테스트
./gradlew build
./gradlew test

최종구조

그래서 좋은점은 뭐였어?

모듈 간 의존성

integration 모듈에서 다른 모듈 참조

// integration/build.gradle
dependencies {
    implementation project(':postgres')  // postgres 모듈의 Entity와 Repository
    implementation project(':redis')     // redis 모듈의 캐시 서비스
    
    // 필요한 추가 의존성들
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

이렇게 하면 integration 모듈에서:

  • postgres 모듈의 User Entity를 그대로 사용
  • redis 모듈의 UserCacheService를 그대로 활용
  • 각 모듈에서 학습한 패턴들을 실제로 통합

코드 재사용

// postgres 모듈에서 정의한 Entity
public class User { ... }

// redis 모듈에서 정의한 캐시 서비스  
public class UserCacheService { ... }

// integration 모듈에서 두 개를 조합
@Service
public class IntegratedUserService {
    // 별도 코드 작성 없이 기존 모듈들의 기능 활용
}

멀티 모듈을 구성하며 느낀점

모듈 분리의 기준

처음에는 기술별로 나누는 것이 당연하다고 생각했다. 하지만 실제로는 학습 목적활용 방식이 더 중요한 기준이었다.

  • postgres 모듈: "PostgreSQL을 어떻게 쓸 것인가"
  • redis 모듈: "Redis를 어떻게 쓸 것인가"
  • integration 모듈: "둘을 어떻게 함께 쓸 것인가"

의존성 관리의 복잡성

멀티 모듈에서 가장 어려운 부분은 의존성 관리였다.

배운 것들:

  • 공통 의존성은 부모에서, 특화 의존성은 자식에서
  • 순환 의존성을 피하기 위한 모듈 설계
  • 버전 충돌 방지를 위한 dependencyManagement 활용

Note

이제 PostgreSQL과 Redis를 실습해보자..!

profile
응애 나 애기 개발자

0개의 댓글