[Spring] 멀티모듈 (2) - 멀티모듈 프로젝트 진행시 생길 수 있는 이슈들 정리(feat. implementation, configuration, component scan)

implementation은 의존성 전파가 되지 않는다. 

문제 상황

db 모듈에는 Jpa 관련 dependency를 implementation으로 추가했다.

api 모듈에서는 추가하지 않은 상태에서 사용하려고 하면 당연히 에러가 발생한다. 

이유는 api 모듈에서는 jpa 관련 아무런 정보(의존성)가 없기 때문이다.

 

해결 방법

방법 1. api 모듈에도 jpa 의존성을 추가 (권장 o)

dependencies {
    implementation project(':db')
}

 

방법 2. db에 api 스코프로 의존성을 추가 (권장 x)

// db 모듈의 build.gradle
dependencies {
    api 'org.springframework.boot:spring-boot-starter-data-jpa'
}

만약 api를 사용하려는데 해당 에러가 발생한다면?

api 키워드를 사용하려면 build.gradle의 플러그인을 추가해줘야한다. 

 

plugins {
	...
	id 'java-library'
}

 

 

implementation과 api 관련해서는 여기에다가 포스팅해놨으니 참고바란다.

https://k9want.tistory.com/entry/Gradle-dependency-implementation%EA%B3%BC-api-%EC%B0%A8%EC%9D%B4

 

[Spring] Gradle dependency - implementation과 api 차이

들어가기 한 프로젝트 안에 멀티모듈을 설정하면서 implementation에 대해 알아볼 필요가 있다고 생각했다. 그래서 이번에는 Gradle dependency 중에 implementation을 알아보고 api의 차이를 정리해보려고 한

k9want.tistory.com

 

 

다른 모듈의 Bean을 쓰고 싶다면? config

 

문제 상황

스프링의 컴포넌트 스캔은 해당 애플리케이션을 실행(@SpringBootApplication)시키는 패키지를 기준으로 컴포넌트(빈) 스캔이 일어난다.

즉, 스프링은 해당 애플리케이션을 실행시키는 자신과 동일한 경로에 있는 패키지에 있는 Bean들을 자신의 Bean으로 등록한다. 

ex) ApiApplication의 패키지를 기준으로 컴포넌트 스캔이 일어난다. (기준 패키지 : org.delivery.api)

 

여기서 문제가 발생한다.

문제는 api에서 db의 AccountRepository(Bean)을 사용하고 싶은데 패키지명이 달라서 api에서 사용할 수가 없다. 

(org.delivery.api != org.delivery.db)

 

해결 방법

방법1. config를 사용해서 db 하위의 컴포넌트를 api에서 빈으로 등록하기

api 모듈의 config클래스에서 @ComponentScan 등과 같은 어노테이션을 사용하여 db 모듈의 패키지를 명시적으로 스캔 대상에 포함시킬 수 있다. 이렇게 하면 db 모듈의 AccountRepository 컴포넌트를 api 모듈의 스프링 컨텍스트에 빈으로 등록할 수 있다.

 

[자동 빈 등록 방식- db 관련일 경우]

db에는 Entity와 Repository 관련된 내용만을 둘 예정이기에 이렇게 작성했다.

config 패키지에서 @Configuration 클래스를 만들거나

아니면 @Configuration -> @SpringBootConfiguration -> @SpringBootApplication


즉, @Configuration을 어노테이션을 상속받고 있는

@SpringBootApplication 어노테이션이 있는 main 메서드에서 명시적으로 지정하여 

스프링이 자동으로 빈을 등록하고 스캔할 수 있게끔 할 수 있다. 

 

 

[자동 빈 등록 방식 - common]

@SpringBootApplication의 scanBasePackages에 컴포넌트 스캔할 패키지들을 명시적으로 작성하여 자동 빈 등록이 되게끔 할 수 있다. 

 

 

[직접 빈 등록 방식 예시] @Bean 어노테이션을 사용

@Configuration
public class ApiModuleConfig {

    @Bean
    public AccountRepository accountRepository() {
        // AccountRepository 인스턴스 생성 또는 참조하여 반환
    }
}

 

중요한 건 다른 모듈에서 정의된 컴포넌트를  현재 모듈의 스프링 컨텍스트에 빈으로 등록하고 싶을 때에는 config를 통해서 빈을 등록하고 사용할 수 있다는 것이다.

 

더보기

스프링 프레임워크에서 '빈으로 등록한다'는 것은 해당 객체를 스프링 애플리케이션 컨텍스트에서 관리할 수 있는 객체로 만들고, 이를 애플리케이션 전반에서 필요할 때 재사용할 수 있게 한다는 의미입니다. 스프링 컨텍스트에 빈으로 등록된 객체는 스프링의 의존성 주입(Dependency Injection) 기능을 통해 다른 컴포넌트에 주입되어 사용될 수 있습니다.

빈으로 등록된 객체는 다음과 같은 특징을 가집니다:

  1. 싱글턴 관리: 기본적으로 스프링은 빈을 싱글턴으로 관리합니다. 즉, 애플리케이션 컨텍스트 내에서 요청될 때마다 동일한 인스턴스를 반환합니다. 이는 메모리 사용을 최적화하고, 인스턴스 간의 공유 상태를 관리하기 용이하게 합니다.
  2. 의존성 주입: 빈으로 등록된 객체는 스프링의 의존성 주입 기능을 통해 자동으로 관련된 객체에 주입될 수 있습니다. 이를 통해 모듈 간의 결합도를 낮추고, 코드의 재사용성과 유지 보수성을 향상시킬 수 있습니다.
  3. 생명주기 관리: 스프링 컨텍스트는 빈의 생명주기를 관리합니다. 이는 빈이 생성되고, 사용되며, 소멸하는 과정에 있어서 필요한 로직을 실행할 수 있게 해줍니다. 예를 들어, @PostConstruct와 @PreDestroy 어노테이션을 사용하여 빈의 초기화 및 소멸 로직을 정의할 수 있습니다.

따라서, 특정 컴포넌트(예: AccountRepository)를 빈으로 등록하면, 그 컴포넌트는 스프링의 관리 하에 들어가 애플리케이션 내에서 필요한 곳에 자동으로 주입되어 사용될 수 있습니다. 이는 스프링이 제공하는 IoC(Inversion of Control) 컨테이너의 핵심 기능 중 하나입니다.

 

방법2. 패키지명을 동일하게 유지하기

org.delivery.db -> org.delivery.api 

 

이 방법은 패키지 이름을 통일하여 자동 컴포넌트 스캔의 범위 내에 두는 것이다.

하지만, 이 방법은 모듈 간의 명확한 경계를 흐리게 하며, 오히려 관리가 복잡해질 수 있다.

각 모듈이 독립적인 기능 단위로 존재하는 것이 바람직하며, 패키지 이름을 강제로 일치시키는 것은 권장하지 않는다.

 

방법3. 공통된 path에다가 스프링을 실행시키기

org.delivery.db & org.delivery.api => org.delivery 

 

둘의 공통적인 패키지 기준인 org.delivery에다가 스프링을 실행시키는 Main메서드를 두는 것이다. 

앞서 말햇듯 @SpringBootApplication 즉, 스프링을 실행시키는 녀석을 기준으로 패키지 하위의 있는 녀석들을 빈으로 등록하기에 공통의 Path를 맞추는 것으로도 해결할 수 있다.

이 또한 방법2와 같은 방식이기에 

각 모듈이 독립적인 기능 단위로 존재하는 것이 바람직하며, 패키지 이름을 강제로 일치시키는 것은 권장하지 않는다.

 

추가 방법 

  • @ComponentScan을 사용하는 것은 분리된 모듈 간의 명시적인 의존성을 관리하는 데 도움이 될 수 있지만, 모듈 간의 결합도를 높일 수 있으므로 주의해서 사용해야 한다.
  • 모듈 간의 의존성을 명확히 관리하고자 할 때는, Spring의 @Import 어노테이션을 사용하여 다른 구성 클래스를 직접 가져오는 방법도 고려할 수 있다.
  • 또 다른 방법으로, api 모듈에서 db 모듈의 빈을 직접 @Bean 메서드를 통해 등록하는 방법도 있다. 이 경우, db 모듈의 Java 설정 파일을 api 모듈에서 직접 참조해야 한다.