본문 바로가기
Theory/Architecture

애플리케이션 조립하기

by y.j 2022. 7. 10.
728x90

왜 조립까지 신경 써야 할까?

코드의 의존성이 올바른 방향(도메인 방향)을 가르키기 위해서 유스케이스와 어댑터를 필요할 때 인스턴스화 하면 안된다. 유스케이스가 영속성 어댑터를 호출해야 하고 스스로 인스턴스화한다면 코드 의존성이 잘못된 방향으로 만들어진 것이다. 이것이 바로 아웃고잉 포트 인터페이스를 생성한 이유다. 유스케이스는 인터페이스만 알아야하고, 런타임에 이 인터페이스의 구현을 제공 받아야 한다.

그렇다면 객체 인스턴스를 생성할 책임은 누구에게 있을까? 그리고 어떻게 의존성 규칙을 어기지 않으면서 그렇게 할 수 있을까? 위 그림처럼 아키텍처에 대해 중립적이고 인스턴스 생성을 위해 모든 클래스에 대한 의존성을 가지는 설정 컴포넌트가 있어야 한다. 

 

설정 컴포넌트의 역할

· 웹 어댑터 인스턴스 생성
· HTTP 요청이 실제로 웹 어댑터로 전달되도록 보장
· 유스케이스 인스턴스 생성
· 웹 어댑터에 유스케이스 인스턴스 제공
· 영속성 어댑터 인스턴스 생성
· 유스케이스에 영속성 어댑터 인스턴스 제공
· 영속성 어댑터가 실제로 데이터베이스에 접근할 수 있도록 보장  

 

평범한 코드로 조립하기

인스턴스화가 필요한 모든 객체를 main함수에 다 넣고 마지막에 웹 컨트롤러를 HTTP로 노출시킨다.

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);
    }
}

 

스프링의 클래스패스 스캐닝으로 조립하기

스프링 클래스패스 스캐닝으로 클래스패스에서 접근 가능한 모든 클래스를 확인해서 @Component 어노테이션이 붙은 클래스를 찾는다.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface PersistenceAdapter {

  /**
   * The value may indicate a suggestion for a logical component name,
   * to be turned into a Spring bean in case of an autodetected component.
   * @return the suggested component name, if any (or empty String otherwise)
   */
  @AliasFor(annotation = Component.class)
  String value() default "";

}

AccountPersistenceAdapter는 필요로 한 모든 필드를 인자로 받는 생서자를 가지고 있어야 한다.

@RequiredArgsConstructor
@PersistenceAdapter
class AccountPersistenceAdapter implements
      LoadAccountPort,
      UpdateAccountStatePort {

   private final SpringDataAccountRepository accountRepository;
   private final ActivityRepository activityRepository;
   private final AccountMapper accountMapper;

 

클래스 스캐닝 방식의 단점

  • 프레임워크에 특화된 것이기 때문에 다른 개발자들이 사용할 라이브러리나 프레임워크를 만드는 입장에서는 사용하지 말아야 할 방법이다. 의조성이 엮이게 될 수 있다.
  • 어플리케이션 컨텍스트에 올라가지 않았으면 하는 클래스가 올라가 추적하기 어려운 에러를 발생시킬 수 있다.

 

스프링의 자바 컨피그로 조립하기

모든 영속성 어댑터들의 인스턴스를 담당하는 설정 클래스를 하나 만든다.

@Configuration
@EnableJpaRepositories
public class PersistenceAdapterConfiguration {

    @Bean
    AccountPersistenceAdapter accountPersistenceAdapter(
            AccountRepository accountRepository,
            ActivityRepository activityRepository,
            AccountMapper accountMapper) {
        return new AccountPersistenceAdapter(
                accountRepository,
                activityRepository,
                accountMapper);
    }

    @Bean
    AccountMapper accountMapper() {
        return new AccountMapper();
    }
}

@Configration도 여전히 클래스패스 스캐닝을 사용하고 있지만, 모든 빈을 가지고 오는 대신에 설정클래스만 선택하기 때문에 추적하기 어려운 에러가 발생할 확류을 줄일 수 있다. @EnableJpaRepostiory는 레파지토리 객체를 가지고 온다. 위 클래스는 빈이 어떻게 등록할지에 대해 제어할 수 있게 된다. 이 방법으로 어댑터, 어플리케이션 계층의 특정 모듈을 위한 설정 클래스를 만들 수도 있다.

 

단점

설정 클래스가 같은 패키지에 존재하지 않는다면 public으로 만들어야 한다.

728x90

'Theory > Architecture' 카테고리의 다른 글

의식적으로 지금길 사용하기  (0) 2022.07.12
아키텍처 경계 강화하기  (0) 2022.07.11
경계 간 매핑하기  (0) 2022.07.09
아키텍처 요소 테스트하기  (0) 2022.07.05
영속성 어댑터 구현하기  (0) 2022.07.02

댓글