Multi-Module 에서의 서로 다른 Module 의 의존성 주입

모두·2025년 2월 14일

안녕하세요 이번 포스팅에서는 프로젝트를 진행하면서 마주친 Muliti-Module 의존성 주입에 대한 오류와 이를 해결하면서 알게된 내용들을 정리하려고 합니다.

Mulit-module 로 설계 하는 이유

많은 개발자들은 Monolitic 방식의 개발보다는 대다수의 서비스가 Multi-Module 방식의 개발을 선호하는 추세입니다.

그렇다면 왜 번거롭게 Multi-Module 을 사용해야할까요?

  1. 모듈성과 재사용성 : Mutli-Module 은 기능 또는 역할에 따라 프로젝트를 분리함으로써 각 모듈은 독립적으로 개발하고 테스트 할 수 있습니다. 이는 코드의 재사용성을 향상시키고 유지보수를 용이하게 합니다.

  2. 독립적인 빌드와 배포 : 각 모듈은 개별적으로 빌드 및 배포될 수 있으므로 전체 프로젝트의 빌드 및 배포 시간을 단축시킬 수 있습니다.

  3. 개발 효율성 : 각 모듈은 독립적인 개발 프로세스를 가지므로, 여러 개발자가 동시에 다른 모듈에 대한 작업을 수행할 수 있습니다.

  4. 테스트 용이성 : 기능 별로 나누어진 각 모듈은 테스트를 독립적으로 실행할 수 있어 테스트 측면에서 용이합니다.

이처럼 Multi-module 방식의 개발은 다양한 장점을 가집니다.

의존성 주입 문제

Multi-module 로 개발을 진행하지만 각 모듈은 의존관계를 가질 수 있습니다. 아래 사진처럼 루트프로젝트에서 'user-api' 라고 하는 모듈을 만들게 되면

root 프로젝트의 settings.gradle에 'user-api'가 include가 된 걸 확인할 수 있습니다.

IntelliJ의 Gradle Tab에서도 보면 다음과 같이 user-api가 root 하위에 존재하는 것을 확인할 수 있다.

그리고 user-api 의 build.gradle 에 필요한 의존성들을 root의 build.gradle 에서 가져와서 넣어준다. 아래는 의존성을 추가해준 build.gradel(user-api)이다.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.2'
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.backend'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

tasks.named('test') {
    useJUnitPlatform()
}

이후 user-api 모듈안에 폴더를 구성하고 UserApplication 을 만들어 준다. 그러면 아래와 같이 형성되고

스프링에서 UserApplication 을 찾아서 실행 시킬 수 있도록 아래처럼 코드를 작성후 실행 시켜보면 잘 실행됨을 확인 할 수 있다.

다른 모듈의 의존성은 런타임시에 추가된다

그렇다면 이렇게 만든 모듈은 의존하는 모듈의 관련 모든 어노테이션을 사용할 수 있지 않을까?
이러한 저의 생각은 프로젝트에서 의존성 주입 문제를 일으켰습니다.

결론은 다른 모듈의 의존성을 참조한다고 해도 어노테이션을 사용하지 못했습니다
서로 다른 모듈에 있는 의존성을 추가할 때, 의존성은 런타임 시에 추가됩니다. 이는 Gradle 의 의존성 관리 매커니즘에 따라 동작합니다

의존성은 보통 프로젝트의 빌드 스크립트에서 선언됩니다.
위의 상황을 기반으로 생각해봅시다.

Gradle 은 참조값을 넣어준 모듈의 build.gradle 에 implementation(project(":user-api")) 을 보고 이 모듈이 domain 모듈에 의존한다는 사실을 알게 됩니다. 그러면 런타임 시에 Gradle 은 이 모듈이 user-api 모듈을 참조할 수 있도록 필요한 라이브러리 및 클래스를 로드합니다

하지만 어노테이션은 주로 컴파일 시점에 처리되는 메타데이터입니다.
즉, 어노테이션은 컴파일 시점에 Spring 프레임워크 혹은 기타 관련 라이브러리에 의해 해석되고 처리됩니다.

정리하자면 이 모듈에서 user-api 모듈을 참조해서 user-api 모듈의 의존성을 사용할 때 런타임 시점에 로드하기 때문에 어노테이션에는 반영되지 못합니다.

해결책은?

예를 들어 jpa에 대한 걸 내가 사용하고 싶다면
그래서 저는 user-api 모듈에 build.gradle 에 jpa 관련 의존성을 별도로 추가했습니다.

이렇게 별도로 jpa 의존성을 추가해줬기 때문에 이 모듈에서는 jpa 관련 어노테이션을 사용할 수 있게 되었습니다.

멀티모듈 Application 의존성 주입

  • 우리는 서비스를 구현할 때 Application 클래스를 하나 만들어서 서버를 실행 시킬 모듈 또 그 서버에 해당하는 domain 만 모아서 둘 모듈을 만들곤 하는데 이 때 의존성을 주입받는 방법을 작성해보려 한다.

  • 아래에서 서버를 실행시킬 모듈은 user-api 이고 여기로 의존성을 가져올 친구는 cms-domain 이라는 모듈이다 .

  • 해당 모듈이 Spring Boot Application 실행 파일이 아니라 라이브러리 모듈로 사용될 때 아래와 같이 설정을 해준다. 즉, 이 모듈은 다른 애플리케이션에서 의존성으로 포함되어 사용되기만 하고, 독립적으로 실행 가능한 JAR 파일을 만들 필요가 없을 때 이렇게 설정합니다.

  • 이 프로젝트에서 서버를 실행시키지 않고 모듈로만 사용되는 cms-domain 모듈의 build.gradle 에 아래 의 bootJar 와 jar 에 대한 설정을 해준다.

이 코드는 Gradle 빌드 스크립트에서 멀티모듈 프로젝트에서 bootJar와 jar 태스크의 활성화 여부를 설정하는 부분입니다.

bootJar는 Spring Boot 프로젝트에서 실행 가능한 JAR 파일을 생성하는 태스크입니다. enabled = false는 해당 모듈에서 bootJar 태스크를 비활성화하여, Spring Boot의 실행 가능한 JAR 파일을 만들지 않도록 합니다.

jar는 일반적인 JAR 파일을 생성하는 태스크입니다. enabled = true는 해당 모듈에서 jar 태스크를 활성화하여, 실행 가능한 JAR이 아닌 일반 JAR 파일을 생성하도록 설정합니다.

Application 실행 모듈

  • 실행 모듈인 user-api 의 build.gradle 에 아래와 같은 코드를 추가하여서 의존성 주입해준다.
    - implementation(project(path: ":cms-domain", configuration: 'default'))

그렇다면 이제 user-api 모듈에서 코드를 작성할 때 cms-domain 모듈에 작성된 파일들을 불러 올 수 있다.

예제 활용

  • 현재 user-api 안의 SignInApplication 에서 cms-domain 에서 작성된 객체인 JwtAuthenticationProvider 을 잘 받아와서 쓰고 있는 것을 볼 수 있다.

  • 하지만 이때 불러온 JwtAuthenticationProvider 의 에러를 보면 Bean이 생성이 안되는데 이는 우리가 다른 모듈에서 끌고온 객체이기에 Bean이 자동으로 생성이 안되어서 그렇다.

  • 그래서 user-api 안에 JwtConfig 라는 클래스를 만들어서 우리가 Autowire 했을 때 제대로 Bean이 생성 될 수 있도록 객체를 생성 해주었다.

0개의 댓글