csct3434
[만들면서 배우는 클린 아키텍처] 09. 애플리케이션 조립하기 본문
자바, 스프링, 스프링부트 프레임워크에서의 의존성 주입 메커니즘에 대해 살펴본다.
(이미지 출처 : https://rudaks.tistory.com)
- 모든 의존성이 안쪽으로, 애플라케이션의 도메인 코드 방향으로 향해야 도메인 코드가 바깥 계층의 변겨응로부터 안전하다
- 한 클래스가 필요로 하는 모든 객체를 생성자로 전달할 수 있다면 실제 객체 대신 목을 전달할 수 있고, 이렇게 되면 격리된 단위 테스트를 생성하기가 쉬워진다
- 우리의 객체 인스턴스를 생성할 책임은 누구에게 있을까? 그리고 어떻게 의존성 규칙을 어기지 않으면서 그렇게 할 수 있을까?
- 아키텍처에 중립적이고 인스턴스 생성을 위해 모든 클래스에 의존성을 가지는 설정 컴포넌트가 그 책임을 갖는다
- 설정 컴포넌트의 역할
- 이는 단일 책임 원칙을 위반하지만, 애플리케이션의 나머지 부분을 깔끔하게 유지하고 싶다면 이처럼 구성요소들을 연결하는 바깥쪽 컴포넌트가 필요하다.
package copyeditor.configuration;
class Application {
public static void main(String [] args) {
AccountRepository accountRepository = new AccountRepository();
ActivityRepository acitivityRepository = new ActivityRepository();
AccountPersistenceAdapter accountPersistenceAdapter =
new AccountPersistenceAdapter(accountRepository, activityRepository);
SendMoneyUseCase sendMoneyUseCase =
new SendMoneyUseService(
accountPersistenceAdapter, // LoadAccountPort
accountPersistenceAdapter); // UpdateAccountStatePort
SendMoneyController sendMoneyController =
new SendMoneyController(sendMoneyUseCase);
startProcessingWebRequests(sendMoneyController);
}
}
- 의존성 주입 프레임워크의 도움 없이 설정 컴포넌트를 구현하는 방법이다.
- main 메서드 안에서 필요한 모든 클래스의 인스턴스를 생성한 후 함께 연결한다.
- 마지막으로 웹 컨트롤러를 HTTP로 노출하는 커스텀 메서드인 startProcessingWebRequests()를 호출한다.
- 단점
- 작성해야 할 코드의 양이 많고 복잡하다.
- 각 클래스가 속한 패키지 외부에서 인스턴스를 생성하기 때문에 이 클래스들은 전부 public이어야 한다.
이렇게 되면 가령 유스케이스가 영속성 어댑터에 직접 접근하는 것을 막지 못한다.
- 대안
- 의존성 주입 프레임워크를 사용한다면 package-private 의존성을 유지할 수 있다.
- 자바 세계에서는 그중 스프링 프레임워크가 가장 인기있다.
- 무엇보다도 스프링은 웹과 데이터베이스 환경을 지원하기 때문에 신비한 startProcessingWebRequests() 메서드 같은 것을 직접 구현할 필요가 없다.
- 애플리케이션 컨텍스트(application context) : 스프링 프레임워크를 이용해서 애플리케이션을 조립한 결과
- 빈(bean) : 애플리케이션 컨텍스트에 포함된 애플리케이션을 구성하는 모든 객체
- 스프링은 애플리케이션 컨텍스트를 조립하기 위한 몇가지 방법을 제공한다
@RequiredArgsConstructor
@PersistenceAdapter
class AccountPersistenceAdapter implements
LoadAccountPort,
UpdateAccountStatePort {
private final AccountRepository accountRepository;
private final ActivityRepository activityRepository;
private final AccountMapper accountMapper;
@Override
public Account loadAccount(
AccountId accountId,
LocalDateTime baselineDate) {
// ...
}
@Override
public void updateActivities(Account account) {
// ...
}
}
- 스프링은 클래스패스 스캐닝으로 클래스패스에서 접근 가능한 모든 클래스를 확인해서 @Component 애너테이션이 붙은 클래스의 인스턴스를 생성하고 애플리케이션 컨텍스트에 등록한다.
- 이때, 각 클래스는 필요한 모든 필드를 인자로 받는 생성자를 가지고 있어야 한다.
- 스프링이 인식할 수 있는 애너테이션을 직접 만들 수도 있다.
-
@Target({ElementType.Type}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface PersisteneAdapter { @AliasFor(annotation = Component.class) String value() default ""; }
- 단점
- 스프링에 특화된 애너테이션
- 코드를 특정한 프레임워크와 결합시킨다.
- 일반적인 애플리케이션 개발에서 애너테이션 하나 정도는 용인할 수 있고, 리팩토링도 그리 어렵지 않다.
- 하지만 라이브러라나 프레임워크를 만드는 입장에서는 사용하지 말아야 한다.
- 에러를 찾는 시간이 오래 걸림
- 스프링 전문가가 아니라면 원인을 찾는 데 수일이 걸릴 수 있는 숨겨진 부수효과를 야기할 수도 있다.
- 여러분은 애플리케이션에 존재하는 모든 클래스 하나하나에 대해 자세히 아는가? 아마 그렇지 않을 것이다.
- 애플리케이션 컨텍스트에 실제로는 올라가지 않았으면 하는 클래스가 있을 수 있다
- 스프링에 특화된 애너테이션
@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();
}
}
- 클래스패스 스캐닝이 애플리케이션 조립하기의 곤봉이라면 스프링의 자바 컨피그(Java Config)는 수술용 메스다
- 프레임워크를 활용하므로 모든 것을 직접 코딩할 필요가 없는 방식이다.
- PersistenceAdapterConfiguration 클래스를 이용해서 영속성 계층에서 필요로 하는 모든 객체를 인스턴스화하는 매우 한정적인 범위의 영속성 모듈을 만들었다.
- @Configuration 애너테이션을 통해 이 클래스가 스프링의 클래스패스 스캐닝에서 발견해야 할 설정 클래스임을 명시한다.
- 빈은 설정 클래스 내의 @Bean 애너테이션이 붙은 팩터리 메서드를 통해 생성된다.
- @EnableJpaRepositories 애터네이션은 스프링 부트가 자동으로 우리가 정의한 모든 스프링 데이터 리포지토리 인터페이스의 구현체를 제공하도록 한다.
- 장점
- 클래스패스 스캐닝과 달리 @Component 애너테이션을 코드 여기 저기에 붙이도록 강제하지 않는다.
따라서, 애플리케이션 계층을 스프링 프레임워크에 대한 의존성 없이 깔끔하게 유지할 수 있다.
- 클래스패스 스캐닝과 달리 @Component 애너테이션을 코드 여기 저기에 붙이도록 강제하지 않는다.
- 단점
- 설정 클래스가 생성하는 빈이 설정 클래스와 같은 패키지에 존재하지 않는다면 이 빈들을 public으로 만들어야 한다.
- 클래스패스 스캐닝은 아주 편리한 기능이다.
- 하지만, 코드의 규모가 커지면 금방 투명성이 낮아진다.
- 테스트에서 애플리케이션 컨텍스트의 일부만 독립적으로 띄우기가 어려워진다.
- 전용 설정 컴포넌트를 만들면 응집도가 매우 높은 애플리케이션 모듈을 만들 수 있다.
- 하지만 설정 컴포넌트를 유지보수하는 데 약간의 시간을 추가로 들여야 한다.
'개발 서적 > 만들면서 배우는 클린 아키텍처' 카테고리의 다른 글
[만들면서 배우는 클린 아키텍처] 08. 경계 간 매핑하기 (0) | 2024.03.03 |
---|---|
[만들면서 배우는 클린 아키텍처] 07. 아키텍처 요소 테스트하기 (0) | 2024.03.03 |
[만들면서 배우는 클린 아키텍처] 06. 영속성 어댑터 구현하기 (0) | 2024.03.03 |
[만들면서 배우는 클린 아키텍처] 05. 웹 어댑터 구현하기 (0) | 2024.03.03 |
[만들면서 배우는 클린 아키텍처] 04. 유스케이스 구현하기 (0) | 2024.03.03 |