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으로 등록할 수 있게 해 준다.
이 부분에 대해서도 한번 알아가시면 좋을 것 같다.
'📕 Spring Framework > Spring 개념 정리' 카테고리의 다른 글
Spring boot multi datasource 등록 시 주의 사항 (0) | 2024.09.18 |
---|---|
[Spring data JPA] N+1 문제 해결 (2) | 2023.02.28 |
[Reactive Programming] 비동기-논블로킹 프로그래밍 (2) | 2023.02.11 |
[@DataJpaTest] h2 인메모리 db를 이용한 테스트 설정 방법 (0) | 2022.12.21 |
WebFlux는 무엇이고, 왜 나왔고, 언제 쓰이는가? (0) | 2022.08.31 |