Spring Boot 3.x 멀티모듈 프로젝트 의존성 문제 해결 가이드

이번 포스팅은 Spring Boot 3.x로 마이그레이션하면서 멀티모듈 프로젝트에서 겪었던 복잡한 의존성 문제를 해결했던 경험을 공유합니다. 다양한 오류의 원인을 분석하고, 단계별로 해결했던 과정을 자세히 풀어냈으니, 비슷한 문제를 겪고 있다면 이 글이 좋은 가이드가 될 것입니다.


발생한 문제: 빌드 오류의 연속

Spring Boot 3.5.4 기반의 멀티모듈 프로젝트를 빌드하는 과정에서 다음과 같은 오류 메시지들이 잇달아 발생했습니다.

  • Could not resolve: io.projectreactor:reactor-netty:1.1.13
  • package jakarta.servlet does not exist
  • package javax.annotation does not exist
  • package org.springframework.security.access.prepost does not exist
  • incompatible types: CompletableFuture<Newses> cannot be converted to Newses

근본 원인 분석: 4가지 문제점

다양한 오류가 발생했지만, 이들은 결국 4가지 핵심 원인으로 귀결되었습니다.

1. Reactor Netty 버전 충돌

  • 원인: 루트 build.gradle 파일에 오래된 reactor-netty:1.1.13 버전을 직접 명시했습니다. 이 버전은 Spring Boot 3.5.4와 호환되지 않았고, Maven Central 리포지토리에서도 찾을 수 없는 문제가 있었습니다. 멀티모듈 구조에서는 루트 프로젝트에 설정된 버전이 하위 모듈에 영향을 주면서 충돌을 일으켰습니다.

2. Jakarta EE 마이그레이션 미완료

  • 원인: Spring Boot 3.x는 **Java EE(Java Enterprise Edition)**에서 Jakarta EE로 완전히 전환되었습니다. 이 과정에서 javax.* 패키지 이름이 jakarta.*로 변경되었는데, 필요한 Jakarta EE 의존성이 누락되어 package jakarta.servlet does not exist와 같은 오류가 발생했습니다.

3. 멀티모듈 의존성 관리 부재

  • 원인: 각 모듈이 독립적으로 의존성을 관리하면서 버전 불일치와 충돌이 발생했습니다. 특히, 루트 프로젝트에서 지정한 특정 버전이 하위 모듈의 의존성 해결 과정에 개입하여 혼란을 가중했습니다.

4. 비동기 처리 타입 불일치

  • 원인: 서비스 레이어에서는 비동기 처리를 위해 CompletableFuture<Newses> 객체를 반환했습니다. 그러나 이를 호출하는 컨트롤러 레이어에서는 비동기 작업의 완료를 기다리지 않고 바로 Newses 타입으로 받으려 했기 때문에 타입 불일치 오류가 발생했습니다.

문제 해결 과정: 5단계 전략

문제를 체계적으로 해결하기 위해 다음의 5가지 단계를 따랐습니다.

1단계: 의존성 버전 강제 설정

  • 문제: 하위 모듈에서 최신 버전을 설정해도 루트 프로젝트에 명시된 1.1.13 버전이 계속 참조되었습니다.
  • 해결: Gradle의 resolutionStrategy.force 기능을 활용하여 모든 서브프로젝트에 최신 버전을 강제로 적용했습니다.
// 루트 build.gradle
subprojects {
    configurations.all {
        resolutionStrategy {
            force(
                'io.projectreactor.netty:reactor-netty:1.2.9',
                'io.projectreactor.netty:reactor-netty-core:1.2.9',
                'io.projectreactor.netty:reactor-netty-http:1.2.9'
            )
        }
    }
}
  • 설명: 이 코드는 Gradle의 의존성 해결 순서에서 가장 높은 우선순위를 가지며, 모든 하위 모듈의 간접 의존성까지 지정된 최신 버전으로 통일시킵니다.

2단계: Jakarta EE 의존성 추가

  • 문제: Spring Boot 3.x에서 필수적인 Jakarta EE API 의존성이 누락되었습니다.
  • 해결: core 모듈에 Jakarta API 의존성을 추가했습니다.
// core/build.gradle
dependencies {
    implementation 'jakarta.servlet:jakarta.servlet-api'
    implementation 'jakarta.annotation:jakarta.annotation-api'
}
  • 설명: Spring Boot 3.x는 Jakarta EE 9+를 기반으로 작동하기 때문에, Servlet이나 Annotation과 같은 핵심 기능을 사용하려면 반드시 이 API들을 명시적으로 추가해야 합니다.

3단계: 패키지명 수정

  • 문제: 코드 내에 javax 패키지명이 그대로 남아있었습니다.

  • 해결: 모든 import 문을 **javax.*에서 jakarta.***로 변경했습니다.

  • 예시:

    // 변경 전:
    import javax.annotation.PostConstruct;
    
    // 변경 후:
    import jakarta.annotation.PostConstruct;
  • 설명: 이는 Java EE에서 Jakarta EE로의 패키지명 변경 규칙을 따르는 가장 기본적인 마이그레이션 작업입니다.

4단계: Spring Security 의존성 추가

  • 문제: web 모듈에서 @PreAuthorize 애노테이션을 찾지 못하는 오류가 발생했습니다.
  • 해결: web/build.gradleSpring Security 스타터 의존성을 추가했습니다.
  • 예시:
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-security'
    }
  • 설명: @PreAuthorize는 Spring Security의 기능이므로, 해당 기능을 사용하려면 관련 의존성이 반드시 필요합니다.

5단계: CompletableFuture 동기 처리

  • 문제: 비동기 작업의 결과인 CompletableFuture 객체를 동기적인 변수 타입(Newses)에 할당하려 했습니다.

  • 해결: CompletableFuturejoin() 메서드를 사용해 비동기 작업이 완료될 때까지 현재 스레드를 블로킹하고 결과값을 가져왔습니다.

  • 예시:

    // 변경 전:
    Newses newses = newsCommandUseCase.scrapNewses(); // 타입 불일치
    
    // 변경 후:
    Newses newses = newsCommandUseCase.scrapNewses().join(); // 동기 대기
  • 설명: join() 메서드는 비동기 작업의 결과를 반환하며, 작업 중 예외가 발생하면 예외를 던집니다.


핵심 교훈 및 팁

이번 경험을 통해 다음의 세 가지 핵심 전략을 배웠습니다.

  1. 중앙 집중식 의존성 관리: 멀티모듈 프로젝트에서는 루트 build.gradle에서 모든 의존성 버전을 통합 관리하는 것이 가장 안정적인 방법입니다.
  2. Gradle resolutionStrategy.force 활용: 복잡한 의존성 충돌이 발생했을 때는 이 기능을 사용하여 특정 버전을 강제로 지정해 문제를 해결할 수 있습니다.
  3. Spring Boot BOM 활용: spring-boot-dependencies BOM(Bill of Materials)을 사용하면 스프링 부트와 호환되는 라이브러리 버전들을 자동으로 맞춰주기 때문에 버전 관리가 훨씬 수월해집니다.

마이그레이션 체크리스트

Spring Boot 3.x로 마이그레이션할 때 꼭 확인해야 할 사항입니다.

  • ✅ Java 17+ 버전을 사용하고 있는지 확인하세요.
  • javax.* 패키지 import문을 모두 jakarta.*로 변경했는지 확인하세요.
  • ✅ 필요한 Jakarta EE API 의존성이 추가되었는지 확인하세요.
  • ✅ 프로젝트에서 사용하는 모든 라이브러리가 Spring Boot 3.x와 호환되는 최신 버전인지 확인하세요.

유용한 트러블슈팅 명령어

  • 의존성 트리 확인: gradle dependencies --configuration compileClasspath
  • Gradle 캐시 초기화: gradle clean --refresh-dependencies (의존성 문제가 해결되지 않을 때 유용합니다)

결론

Spring Boot 3.x로의 마이그레이션은 단순히 버전만 올리는 작업이 아니라, Java EE에서 Jakarta EE로의 전환을 이해하고 그에 맞춰 전체적인 프로젝트 설정을 재정비하는 과정이었다. 특히 멀티모듈 프로젝트에서는 의존성 관리가 복잡해질 수 있으므로, 중앙 집중식 관리Gradle의 고급 기능을 활용하는 것이 필수적이라는것을 이번 경험을 통해 마이그레이션 작업에 도움이 되었다.

profile
에러가 나도 괜찮아 — 그건 내가 배우고 있다는 증거야.

0개의 댓글