본문 바로가기
📕 Spring Framework/Spring 개념 정리

Bean 등록에 대한 재고

by GroovyArea 2024. 10. 12.

SpringBoot에서 Bean을 등록하는 방법은 무엇이 있을까.
 
1. 클래스에 직접 @Component 애노테이션을 사용하는 방식
2. @Configuration 애노테이션을 활용해 @Bean 등록하는 방식
 
위 2가지 방식이 보편적이다.
내가 지금까지 진행했던 보통의 애플리케이션 개발 과정에서는 1번을 압도적으로 많이 사용했던 것 같다.
@Configuration 방식의 경우, 통상 외부 라이브러리를 Bean으로 등록하기 위함이라며 면접 질문에서 정석적으로 회자된다.
그 고정 관념 탓에, 나의 경우도 2번 방식은 외부 설정 이외에 사용하지 않았었다.
 
Java9에서 나온, 새로운 추상화 개념인 모듈이라는 개념이 있다.
모듈에는 종속성(dependency)의 개념이 있으며, Public API를 내보내고 구현 세부 정보를 숨김/비공개 상태로 유지할 수 있다.
 
보통의 프로젝트는 간단히 싱글 모듈 구조로 구성되어 있기에, 프로젝트 당 애플리케이션 1개이다.
단일 모듈 내에 존재하는 모든 Bean 들이 앱이 실행될 때 로드 된다.
크게 Bean 등록에 대한 거부감 없이 모두 등록을 했었다. 애플리케이션에 당연히 필요하니.
 
멀티 모듈 프로젝트의 경우, 프로젝트 하나에 여러 개의 애플리케이션이 존재할 수 있다.
api application, batch application, consumer application 등등.. 여러 Jar가 말아진다.
 
이 단계에서 부터는 모듈 간의 의존성에 더 고민을 해보게 된다.
단일 모듈의 경우, 패키지로 이 의존성을 개념적으로는 분리할 수 있었지만 물리적으로 분리할 수는 없다.
멀티 모듈 프로젝트에서 각 애플리케이션의 경우, 각자 필요로 하는 모듈 의존성이 있기 마련이므로 어떤 의존성을 갖게 할지 고민하게 된다. 물론 패키지로 의존성 분리와 별반 다르지 않다고 생각이 들긴 한다.
 
아래 예시 멀티 모듈들을 살펴보자.
- api (Jar)
- batch (Jar)
- core
- infrastructure
- consumer (Jar)
- 중략..
 
각각의 Jar 가 말아질 때면, 각자 필요한 Bean만 등록되어야 한다.
core 모듈의 경우, 보통 도메인과 비즈니스 로직 등이 존재한다.
이에 api, batch, consumer 등 usecase 를 필요로 하는 module의 경우 이 core 의존성이 필요할 것이다.
api, batch 등의 모듈에서 core 를 참조해서 usecase를 수행한다. 즉, core를 참조하며 주입받아서 수행한다.
core에 필요 없는 Dependency는 무엇일까? 순수하게 POJO 객체만 존재할 것이므로 Spring Starter 의존성은 필요하지 않을 것이다.
그럼 어떻게 Bean을 등록해서 사용해야 될까? @Component 도 안될 텐데..?
 
Configuration 가 있지 않은가. core 가 외부 라이브러리도 아닌데, 굳이?라는 생각이 들 수도 있다.
그 또한 서두에 얘기했던 나의 고정관념으로부터 비롯된 오판이었다.
필요한 만큼 각 모듈에서 core를 Bean으로 등록하여 사용하면 될 것이다. 꼭 필요한 Bean 만 로드 되게.
어떤 모듈에서는 core 의존성이 필요가 없을 텐데, 그때는 훨씬 더 가벼운 Jar 가 될 것이다. 
 
예시로 나는 아래와 같이 사용한다.
api 모듈에 필요한 core나 infra 의존성을 직접 필요한 만큼만 bean 등록하여 사용하는 것이다.
infrastructure 모듈에서는 spring 의존성을 제거하긴 쉽지 않을 것이다.
 
필요한 bean 만 최소로 등록하고, 나머지는 일반 Class 로만 내버려두는 것이 훨씬 바람직할 것이다.
정말 이 객체가 Bean으로 등록되어야 하는지에 대해 한번 더 생각을 해봐야 된다는 것이다. 
 

// api 모듈에 위치한 Factory
// 하기 bean 들은 모두 infrastructure 모듈의 객체들이다.

@Configuration
class ExternalHttpBeanFactory(
    private val apiClient: ApiClient
) {

    @Bean
    fun httpGetRevenueDailyData(): HttpGetRevenueDailyData = HttpGetRevenueDailyData(apiClient)

    @Bean
    fun httpGetRevenueMonthlyData(): HttpGetRevenueMonthlyData = HttpGetRevenueMonthlyData(apiClient)

    @Bean
    fun httpGetRevenueExpectedAmount(): HttpGetRevenueExpectedAmount = HttpGetRevenueExpectedAmount(apiClient)

    @Bean
    fun httpGetRevenueTotalAmount(): HttpGetRevenueTotalAmount = HttpGetRevenueTotalAmount(apiClient)

    @Bean
    fun httpGetCostsCalendar(): HttpGetCostsCalendar = HttpGetCostsCalendar(apiClient)

    @Bean
    fun httpGetCostMonthly(): HttpGetCostMonthly = HttpGetCostMonthly(apiClient)

    @Bean
    fun httpGetCostEstimated(): HttpGetCostEstimated = HttpGetCostEstimated(apiClient)
}

 

추가로 Spring Boot에서는 @Conditional~~ 시리즈의 애노테이션들을 제공한다.
Profile의 값을 통해 특정 조건에서만 Bean으로 등록할 수 있게 해 준다.
이 부분에 대해서도 한번 알아가시면 좋을 것 같다.

반응형