9. 애플리케이션 조립하기
유스케이스, 웹 어댑터, 영속성 어댑터를 구현했으니 이것들이 동작하는 애플리케이션으로 조립해보자.
각 클래스를 그냥 필요할때 인스턴스화 하지 않는 이유는 코드의 의존성이 올바른 방향을 가리키게 하기 위해서다. 모든 의존성은 안쪽, 애플리케이션의 도메인 코드 방향으로 향해야 도메인 코드가 바깥 계층의 변경으로부터 안전하다.
헥사고날 아키텍처 스타일은 하나의 코드를 훨씬 더 테스트하기 쉽다. 한 클래스가 필요로 하는 모든 객체를 생성자로 전달할 수 있다면 실제 객체 대신 목으로 전달할 수 있고, 이렇게 되면 격리된 단위 테스트를 생성하기 쉬워진다.
가장 추천하는 건 객체 인스턴스를 생성할 책임을 모든 클래스에 대한 의존성을 가지는 설정 컴포넌트를 만드는 것이다.
- 각 계층의 어댑터 인스턴스 생성
- HTTP요청, 데이터베이스 접근 등 외부 접근에 대한 전달을 보장
- 각 어댑터에 유스케이스 인스턴스 제공
그 외 설정 파일이나 설정 파라미터의 소스에도 접근해야 한다.
단일 책임 원칙을 위반하게 되지만 나머지 부분을 깔끔하게 유지하기 위해서 구성요소들을 연결하는 바깥쪽 컴포넌트가 필요하다.
스프링 프레임워크를 이용해서 애플리케이션을 조립한 결과물을 애플리케이션 컨텍스트(application context)라고 한다. 애플리케이션 컨텍스트는 애플리케이션을 구성하는 모든 객체(bean)을 포함한다.
일반적으로는 스프링의 클래스패스 스캐닝으로 조립하는데 클래스패스에서 접근 가능한 모든 클래스를 확인해서 @Component 어노테이션이 붙은 클래스를 찾아서 각 클래스의 객체를 생성한다.
이때 필요한 모든 필드를 인자로 받는 생성자가 필요한데 Lombok 라이브러리의 @RequiredArgsConstructor 어노테이션을 사용하면 모든 final 필드를 인자로 받는 생성자를 자동으로 만들수도 있다.
각 클래스들의 인스턴스를 만들어 애플리케이션 컨텍스트에 추가하게 되는데 어노테이션 기반이라 필요한 곳에 작성만 잘하면 손쉽게 애플리케이션을 조립할 수 있지만 몇가지 단점도 존재한다.
- 클래스에 프레임워크에 특화된 어노테이션을 붙여야 한다는 점에서 침투적이다.
일반적인 애플리케이션 개발에선 필요한 경우 한두개 정도는 용인되더라도 다른 개발자들이 사용할 라이브러리나 프레임워크를 만드는 입장이라면 사용자가 스프링 프레임워크의 의존성에 엮이게 되서 쓰지 말아야 한다. - 스프링 전문가가 아니라면 문제가 발생했을 때 원인을 찾는데 많은 시간이 소요될 수 있다.
이는 클래스패스 스캐닝이 단순히 스프링에게 부모 패키지를 알려 준 후 이 패키지 안에서 @Compoment가 붙은 모든 클래스를 찾는 방법이기 때문이다.
애플리케이션 내 모든 클래스를 하나하나 다 알기 어렵다.
좀 더 제어하기 쉬운 대안으로 스프링의 Java Config로 조립하는 방법이 있다.
이 방식은 애플리케이션 컨텍스트에 추가할 빈을 생성하는 설정 클래스를 만든다.
@Configuration 어노테이션을 사용하여 설정 클래스임을 표시하여 클래스패스 스캐닝을 사용하는 방식은 모든 빈을 가져오는 대신 설정 클래스만 선택하기 때문에 제어가 한결 쉽다.
빈 자체는 설정 클래스 내의 @Bean 어노테이션이 붙은 팩토리 메서드를 통해 생성한다.
@EnabledJpaRepositories 어노테이션을 사용하여 스프링 부트가 정의된 모든 스프링 데이터 리포지토리 인터페이스의 구현체를 제공할 것이다.
이 어노테이션은 설정 클래스가 아니라 메인 애플리케이션에도 붙일 수 있지만 그럴 경우 애플리케이션이 시작할 때마다 JPA를 활성화해서 영속성이 실질적으로 필요없는 테스트에서 애플리케이션을 실행할 때도 JPA 리포지토리들을 활성화 할 것이다.
따라서 이러한 기능 어노테이션은 별도의 설정 모듈에 있는 것이 한꺼번에 모든 것이 시작할 필요가 없어져서 애플리케이션을 더 유연하게 만들어 준다.
이러한 방식은 일반적인 클래스패스 스캐닝 방식에 비해 @Component 어노테이션을 코드 여기저기에 붙이도록 강제하지 않아서 애플리케이션 계층을 스프링 프레임워크에 대한 의존성 없이 깔끔하게 유지할 수 있다.
다만, 설정 클래스가 생성하는 빈이 설정 클래스와 같은 패키지에 존재하지 않는다면 이 빈들을 public 으로 만들어야 한다.