[클린 아키텍처] Ch 09. 애플리케이션 조립하기

.·2022년 5월 18일
1

각각 구현한 유스케이스, 웹 어댑터, 영속성 어댑터를 동작하는 애플리케이션으로 조립하기

의존성 주입 메커니즘

  1. 평범한 자바
  2. 스프링
  3. 스프링 부트 프레임워크

9-1. 왜 조립까지 신경 써야 할까?

올바른 의존성 방향

유스케이스와 어댑터를 필요할 때마다 인스턴스화한다면? 의존성이 여러 방향으로 퍼질 수도 있다.

모든 의존성이 애플리케이션 코어(도메인 코드)쪽인 올바른 방향으로 향해야 한다.

이것이 아웃고잉 포트 인터페이스이 존재 이유 - 유스케이스는 인터페이스만 알아야 하고, 런타임에 인터페이스의 구현을 제공받는다.

테스트의 용이성

필요한 모든 객체를 생성자로 받을 수 있다면, 테스트시 목 객체를 받아서 격리된 단위 테스트를 생성하기 쉬워진다.

중립적인 설정 컴포넌트

  • ‘아키텍처에 대해 중립적이고 인스턴스 생성을 위해 모든 클래스에 의존성을 가지는 설정 컴포넌트’가 객체 인스턴스를 생성하고 의존성을 주입해주는 책임이 있다.
  • 모든 내부 계층에 접근할 수 있는 원의 가장 바깥쪽에 위치

역할

1. 웹 어댑터 인스턴스 생성
2. HTTP 요청이 실제로 웹 어댑터로 전달되도록 보장
3. 유스케이스 인스턴스 생성
4. 웹 어댑터에 유스케이스 인스턴스 제공
5. 영속성 어댑터 인스턴스 생성
6. 유스케이스에 영속성 어댑터 인스턴스 제공
7. 영속성 어댑터가 실제로 데이터베이스에 접근할 수 있도록 보장
8. 설정 파일, 커맨드라인 파라미터 등 설정 파라미터 소스에 접근, 애플리케이션 컴포넌트에 제공 (db 설정, 서버 설정, 행동 양식 제어 등)

책임이 굉장히 많다 → 변경할 이유가 많다 → SRP 위반
하지만 나머지 코드를 깔끔하게 유지하기 위해 어쩔 수 없다.

9-2. 평범한 코드로 조립하기

public class CleanArchitectureApplication {

    public static void main(String[] args) {
				AccountRepository accountRepository = new AccountRepository();
				ActivityRepository activityRepository = new ActivityRepository();

        AccountPersistenceAdapter accountPersistenceAdapter = new AccountPersistenceAdapter(accountRepository,
                activityRepository, new AccountMapper());

        SendMoneyUseCase sendMoneyUseCase = new SendMoneyService(
                accountPersistenceAdapter, accountPersistenceAdapter, new NoOpAccountLock(), new MoneyTransferProperties());

        SendMoneyController sendMoneyController = new SendMoneyController(sendMoneyUseCase);

        startProcessingWebRequests(sendMoneyController);
    }
}

main()에서 필요한 모든 클래스의 인스텉스를 생성한 후 연결

단점

  1. 송금하기 예제만 있는 것이 아닌 굉장히 많은 클래스를 가지고 있는 애플리케이션에서는 이러한 코드를 아주 많이 만들어야 한다.
  2. 모든 클래스들은 public이어야 한다.
    • 유스케이스가 영속성 어댑터에 직접 접근하게될 수도 있다.
    • SendMoneyController의 접근제어자를 package-private에서 public으로 변경

Spring Framework

  • Application Context : 애플리케이션을 조립한 결과물
  • Bean : 애플리케이션을 구성하는 모든 객체

애플리케이션 조립 방법

  1. classpath scanning (가장 인기, 편리) = 조립하기의 곤봉
  2. Java Config = 수술용 메스

9-3. 스프링의 클래스패스 스캐닝으로 조립하기

위의 지저분한 작업을 대신해줄 수 있는 의존성 주입 프레임워크 - 그 중 자바의 스프링 프레임워크

방식

스프링은 클래스패스 스캐닝으로 클래스패스에서 접근 가능한 모든 클래스를 확인해서 @Component가 붙은 클래스를 찾고, 이 클래스들의 각 객체를 생성한다.

적절한 곳에 @Component를 붙이고 생성자를 잘 만들기

단점

  1. 클래스에 프레임워크 특화된 어노테이션을 붙여야 한다.
    • 다른 개발자가 사용할 라이브러리나 프레임워크를 만들 때는 해당 기능 사용자가 특정 프레임워크의 의존성에 엮이게 되기 때문에 프레임워크 특화 어노테이션 붙이는 것을 지양해야 한다.
  2. (흑)마법 같은 일
    • 스프링 전문가가 아니라면 숨겨진 부수효과를 찾는 데 며칠이 걸릴 수도 있다.
    • 클래스패스 스캐닝 방식이 애플리케이션을 조립하기에는 너무 둔한 도구이기 때문
    • 우리는 모든 코드를 알지 못한다. 원하지 않는 코드가 애플리케이션 컨텍스트에 올라가서 추적하기 어려운 에러를 발생시킬 수도 있다.

9-4. 스프링의 자바 컨피그로 조립하기

애플리케이션 컨텍스트에 등록할 빈을 생성, 제어하는 설정 클래스

@Configuration
@EnableJpaRepositories(basePackages = "com.woowa.cleanarchitecture.account")
public class PersistenceAdapterConfiguration {
    @Bean
    AccountPersistenceAdapter accountPersistenceAdapter(
            AccountRepository accountRepository,
            ActivityRepository activityRepository,
						AccountMapper accountMapper) {
        return new AccountPersistenceAdapter(accountRepository, activityRepository, accountMapper);
    }

    @Bean
    AccountMapper accountMapper() {
        return new AccountMapper();
    }
}
  • @Configuration: 스프링의 클래스패스 스캐닝에서 발견해야 할 설정 클래스
    • 모든 빈을 가져오는 대신 설정 클래스만 선택한다.
  • factory method 사용해서 bean 생성
  • @EnableJpaRepositories: 스프링 부트가 직접 jpa 리포지토리 인터페이스의 구현체 생성해서 제공
    • 기본적으로 해당 Config 클래스의 하위 패키지를 스캔한다.
      • 위 코드에서는 스캔을 시작할 basePackage 지정
    • 리포지토리 객체가 또 다른 설정 클래스의 팩터리 메서드에서 수동으로 생성됐다면, 스프링이 자동으로 팩터리 메서드의 파라미터로 제공한다.
    • accountPersistenceAdpter()의 파라미터인 accountRepository, activityRepository
    • 이런 기능 애너테이션을 main이 아닌 별도의 설정 모듈로 위치시킨다면 애플리케이션을 더 유연하게 만들고, 항상 모든 것을 한꺼번에 시작할 필요가 없어진다.
      • 메인문에 어노테이션을 적용시키면 영속성이 필요없는 테스트에서도 애플리케이션을 시작할 때마다 JPA를 활성화시키게 된다.

장점

  1. 특정 모듈만 포함한 설정 클래스를 만든다면 다른 모듈의 빈은 모킹해서 애플리케이션 컨텍스트를 만들어 유연한 테스트를 할 수 있다.
  2. 코드에 @Component를 붙이도록 강제하지 않는다.
    • 애플리케이션 계층을 프레임워크 종속적이지 않고 깔끔하게 유지할 수 있다.

단점

  1. 설정 클래스가 생성하는 빈이 설정 클래스와 다른 패키지에 존재한다면 이 빈들을 public으로 만들어야 한다.

9-5. 유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?

클래스패스 스캐닝

(+) 패키지만 알려주면 알아서 스프링이 애플리케이션을 조립하므로 아주 편리한 기능이다.
(-) 코드의 규모가 커지면 어떤 빈이 애플리케이션 컨텍스트에 등록되는지 정확히 알 수 없다.
(-) 테스트할 때 컨텍스트의 일부만 독립적으로 띄우기 어렵다.

configuration 클래스

(+) 애플리케이션의 변경할 이유(책임)이 줄어든다.
(+) 서로 다른 모듈로부터 독립되어 응집도가 매우 높은 모듈을 만들 수 있다.
(-) 유지보수하는데 더 많은 시간을 투자해야 한다.

0개의 댓글