조립까지 신경써야하는 이유는 코드 의존성이 올바른 방향을 가리키게 하기 위해서다. 모든 의존성은 안쪽으로 즉, 애플리케이션의 도메인 코드 방향으로 향해야 바깥 계층의 변경으로부터 안전하다.
객체 인스턴스를 생성할 책임은 설정 컴포넌트가 가진다. 설정 컴포넌트는 클린 아키텍처에서 가장 바깥쪽에 위치하며 우리가 제공한 조각들로 애플리케이션을 조립하는 것을 책임진다. 구체적으로 다음과 같은 역할을 수행해야 한다.
public class Application {
public static void main(String[] args) {
AccountRepository accountRepository = new AccountRepository();
ActivityRepository activityRepository = new ActivityRepository();
AccountPersistenceAdapter accountPersistenceAdapter = new AccountPersistenceAdapter(accountRepository, activityRepository);
SendMoneyUseCase sendMoneyUseCase =
new SendMoneyService(
accountPersistenceAdapter,
accountPersistenceAdapter);
SendMoneyController sendMoneyController = new SendMoneyController(sendMoneyUseCase);
startProcessingWebRequests(sendMoneyController); //웹 어댑터를 HTTP로 노출시키는 데 필요한 애플리케이션 부트스트랩핑 로직이 들어갈 곳
}
}
위 방식은 다음과 같은 단점이 존재한다.
이런 의존성 주입을 해주는 스프링을 사용해보자
스프링을 이용해서 애플리케이션을 조립한 결과물을 애플리케이션 컨텍스트라고 한다. 애플리케이션 컨텍스트는 빈(bean)을 포함한다.
클래스패스 스캐닝은 @Component 애너테이션이 붙은 클래스를 찾아 그 클래스의 객체를 생성한다. 이는 필요한 모든 필드를 인자로 받는 생성자를 가지고 있어야 한다.
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface PersistenceAdapter {
@AliasFor(annotation = Component.class)
String value() default "";
}
위와 같이 커스텀 애너테이션으로 만들어 PersistenceAdapter가 애플리케이션 일부임을 나타낼 수 있다.
이 방식도 다음과 같은 단점이 존재한다.
직접 애플리케이션 컨텍스트에 추가할 빈을 생성하는 설정 클래스를 만드는 것이다.
@Configuration
@EnableJpaRepositories
class PersistenceAdapterConfiguration {
@Bean
AccountPersistenceAdapter accountPersistenceAdapter(
AccountRepository accountRepository,
ActivityRepository activityRepository,
AccountMapper accountMapper
){
return new AccountPersistenceAdapter(
accountRepository,
activityRepository,
accountMapper);
}
@Bean
AccountMapper accountMapper(){
return new AccountMapper();
}
}
@Configuration
을 통해 클래스패스 스캐닝에서 발견해야 할 설정 클래스임을 표시해둔다. 여전히 클래스패스 스캐닝을 사용하는건 맞지만, 모든 빈을 가져오는 대신 설정 클래스만 선택하도록 한다.
@Bean
을 통해 영속성 어댑터를 애플리케이션 컨텍스트에 추가한다. @EnableJpaRepositories
을 통해 스프링 데이터 리포지토리 인터페이스의 구현체를 가져온다.
PersistenceAdapterConfiguration 클래스를 이용해 영속성 계층에서 필요한 모든 객체를 한정적인 범위에서 만들도록 하였다. 이를 통해 우리는 어떤 빈이 애플리케이션 컨텍스트에 등록될지 제어할 수 있게 된다. 또한 코드 전체에 @Component
애노테이션을 붙이지 않아도 돼서 스프링에 대한 종속성 없이 애플리케이션 계층을 깨끗하게 유지할 수 있다.
하지만 위 방식은 설정 클래스와 bean이 같은 패키지에 존재하지 않는다면 bean대상들도 public으로 만들어야 한다.
클래스패스 스캐닝은 어떤 빈이 애플리케이션 컨텍스트에 올라오는지 정확히 알 수 없고, 테스트에서 일부만 독립적으로 띄우기 어려워진다.
전용 설정 컴포넌트를 만들면 애플리케이션이 책임(변경할 이유)로부터 자유로워지며 응집도가 높은 모듈을 만들 수 있다.