왜 조립까지 신경 써야 할까?
코드의 의존성이 올바른 방향(도메인 방향)을 가르키기 위해서 유스케이스와 어댑터를 필요할 때 인스턴스화 하면 안된다. 유스케이스가 영속성 어댑터를 호출해야 하고 스스로 인스턴스화한다면 코드 의존성이 잘못된 방향으로 만들어진 것이다. 이것이 바로 아웃고잉 포트 인터페이스를 생성한 이유다. 유스케이스는 인터페이스만 알아야하고, 런타임에 이 인터페이스의 구현을 제공 받아야 한다.
그렇다면 객체 인스턴스를 생성할 책임은 누구에게 있을까? 그리고 어떻게 의존성 규칙을 어기지 않으면서 그렇게 할 수 있을까? 위 그림처럼 아키텍처에 대해 중립적이고 인스턴스 생성을 위해 모든 클래스에 대한 의존성을 가지는 설정 컴포넌트가 있어야 한다.
설정 컴포넌트의 역할
· 웹 어댑터 인스턴스 생성
· 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으로 만들어야 한다.
'Theory > Architecture' 카테고리의 다른 글
의식적으로 지금길 사용하기 (0) | 2022.07.12 |
---|---|
아키텍처 경계 강화하기 (0) | 2022.07.11 |
경계 간 매핑하기 (0) | 2022.07.09 |
아키텍처 요소 테스트하기 (0) | 2022.07.05 |
영속성 어댑터 구현하기 (0) | 2022.07.02 |
댓글