일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 추상화 설계
- template
- Thymeleaf
- 테스트주도개발
- 객체지향의사실과오해
- Java
- 계층형아키텍처
- Kotlin
- 개발방법론
- 조영호
- 아키텍처
- web
- GrokkingFunctionalProgramming
- 테스트
- 도메인 주도 개발 시작하기
- 개발서적
- Boot Legacy 차이점
- 함수형프로그래밍
- 유지보수
- 만들면서배우는클린아키텍처
- 책스터디
- 클린아키텍처
- Spring
- 코틀린
- DDD
- 스터디
- TDD
- FP
- 이펙티브코틀린
- 헥사고날아키텍처
- Today
- Total
김동형수 개발기
만들면서 배우는 클린 아키텍처 - 09 정리 본문
애플리케이션 조립하기
애플리케이션이 시작될 때 클래스를 인스턴스화하고 묶기 위해서 의존성 주입 메커니즘을 이용한다.
평범한 자바, 스프링, 스프링 부트 프레임워크에서 각각 어떻게 하는지 살펴본다.
왜 조립까지 신경 써야 할까?
- 코드의존성이 올바른 방향을 가리키게 하기 위해서
- 모든 의존성은 안쪽으로 ( 웹 -> 영속성 )
- 유스케이스가 영속성 어댑터를 호출하는 케이스를 만들지 않기 위해서 아웃고잉 포트 인터페이스 생성
- 유스케이스는 인터페이스만 알고(참조하고) 있어야한다.
헥사고날 아키텍처의 유익한 부수효과 중 하나는 코드를 휠씬 쉽게 테스트할 수 있는 것이다.
의존성이 있는 모든 객체를 생성자로 전달할 수 있다면 mock으로 전달할 수 있고 이는 격리된 단위 테스트를 생성하기가 쉬워진다.
설정 컴포넌트는 애플리케이션을 조립하는 것을 책임진다.
- 웹 어댑터 인스턴스 생성
- HTTP 요청이 실제로 웹 어댑터로 전달되도록 보장
- 유스케이스 인스턴스 생성
- 웹 어댑터에 유스케이스 인스턴스 제공
- 영속성 어댑터 인스턴스 생성
- 유스케이스 영속성 어댑터 인스턴스 제공
- 영속성 어댑터가 실제로 데이터베이스에 접근할 수 있도록 보장
설정파일이나 커맨드라인 파라미터 같은 설정 파라미터 소스에도 접근할 수 있어야 한다. 이런 파라미터들을 애플리케이션 컴포넌트에 제공해서 어떤 데이터베이스에 접근하고 어떤 서버를 메일 전송에 사용할지 등의 행동 양식을 제공한다.
LIFIC에선 AWS S3와 연결된 Config Server에서 yml 파일에서 파라미터 설정을 읽어와서 각각의 설정컴포넌트(명확하진 않지만, ㅂ@Configuration 어노테이션을 사용한 설정일 것 같다.)를 설정한다.
단일 책임 원칙을 위반하지만, 작동하는 애플리케이션으로 조립하기 위해서 애플리케이션을 구성하는 모든 움직이는 부품을 알아야한다.
평범한 코드로 조립하기
의존성 주입 프레임워크의 도움 없이 애플리케이션을 만든다면 평범하 코드로 아래와 같은 컴포넌트를 만들 수 있다.
의존성이 있는 모든 인스턴스를 생성 후 startProcessingWebRequest() 메서드를 실행한다.
이 평범한 코드 방식은 애플리케이션을 조립하는 가장 기본적인 방법이다. 하지만 단점이 있다.
- 예시 코드는 단 하나씩의 웹 컨트롤러, 유스케이스, 영속성 어댑터가 있는 코드인데, 개수가 늘어나면 코드량이 많이 늘어난다.
- 외부에서 인스턴스 생성을 위해서 public 접근지정자로 생성해야한다. 원치 않는 의존성을 피하기 package-private 하게 생성했으면 좋았을 것이다.
package-private한 의존성을 유지하면서 지저분한(귀찮은) 작업을 대신해줄 수 있는 의존성 주입 프레임워크들이 있다.
자바에서는 스프링 프레임워크가 인기 있다. 웹과 데이터베이스 환경을 지원하기 때문에 startProcessingWebRequest() 메서드 같은 것을 구현할 필요가 없다.
스프링의 클래스패스 스캐닝으로 조립하기
스프링 프레임워크를 이용해서 애플리케이션을 조립한 결과물을 애플리케이션 컨텍스트라고 한다. 애플리케이션 컨텍스트는 애플리케이션을 구성하는 모든 객체(빈-Bean)를 포함한다.
클래스패스 스캐닝 : 클래스패스에서 접근 가능한 모든 클래스를 확인해서 @Component 어노테이션이 붙은 클래스를 찾는다.
이때 클래스는 필요한 모든 필드를 인자로 받는 생성자를 가지고 있어야 한다.
위 코드에서는 직접 생성자를 만들지 않고 Lombok 라이브러리의 @RequiredArgsConstructor 어노테이션을 이용해 모든 final 필드를 인자로 받는 생성자를 자동으로 생성했다.
Flow
1. 스프링에서는 생성자에 인자로 사용된 @Component가 붙은 클래스를 찾는다.
2. 이 클래스들의 인스턴스를 만들어 애플리케이션 컨텍스트에 추가한다.
3. 필요한 객체가 모두 생성되면 AccountPersistenceAdapter의 생성자를 호출한다.
4. 3에서 생성된 객체도 마찬가지로 애플리케이션 컨텍스트에 추가한다.
클래스 스캐닝 방식을 이용하면 아주 편리하게 애플리케이션을 조립할 수 있다.
스프링이 인식할 수 있는 어노테이션을 직접 만들 수도 있다.
이 어노테이션은 @Component를 포함하고 있어서 스프링이 클래스패스 스캐닝을 할 때 인스턴스를 생성할 수 있도록 한다.
@Component 대신 @PersistenceAdapter를 이용해 영속성 어댑터 클래스들이 애플리케이션의 일부임을 표시할 수 있다.
@PersistenceAdapter 덕에 코드를 더 쉽게 파악할 수 있다.
클래스패스 스캐닝 방식 단점
- 클래스에 프레임워크 특화된 어노테이션을 붙여야 한다.
- 침투적이다.
- 강경한 클린 아키텍처파는 이런 방식이 특정 프레임워크와 결합시키기 때문에 사용하지 말아야한다고 주장
- 다른 개발자들이 사용할 라이브러리나 프레임워크를 만드는 입장에서는 사용하지 말아야 할 방법이다.
- 라이브러리 사용자가 스프링 프레임워크의 의존성에 엮이게 된다.
- 마법이 일어난다.
- 스프링 프레임워크가 아니라면 원익 찾는데 수일이 걸릴 수 있는 숨겨진 부수효과가 생길 수 있다.
- 단순하게 상위 패키지명, @Component 어노테이션을 이용해서 스캔을 하기 때문에 실제로 애플리케이션 컨텍스트에 올라가지 않았으면 하는 클래스가 악의적으로 조작해(의도치 않은 작용?)으로 추적이 어려운 에러를 일으킬 수도 있다.
스프링 자바 컨피그로 조립하기
클래스패스 스캐닝 방식보다 진화된 방식이다. 평범한 코드를 이용하는 방식과 비슷한데, 덜 지저분하고 프레임워크와 함께 제공되므로 모든것을 직접 코딩할 필요가 없는 방식이다.
- @Configuration 어노테이션을 통해 이 클래스가 스프링의 클래스패스 스캐닝에서 발견해야 할 설정 클래스임을 표시
- 클래스패스 스캐닝을 사용하고 있는 것이긴 함
- 모든 빈이 아닌 설정한 클래스만 선택하기 때문에 해로운 마법이 일어날 확률이 줄어든다.
빈 자체는 설정 클래스 내의 @Bean 어노테이션이 붙은 팩터리 메서드를 통해 생성된다.
AccountPersistenceAdapter 클래스는 2개의 리포지토리와 한개의 매퍼를 생성자를 입력으로 받는다.
스프링은 이 객체들을 자동으로 팩터리 메서드에 대한 입력으로 제공한다.
스프링이 이 리포지토리 객체들을 어디서 가져올까?
다른 설정 클래스의 팩터리 메서드에서 수동으로 생성됐다면, 스프링이 자동으로 팩터리 메서드의 파라메터로 제공할 것이다.
그러나 이 예제는 @EnabledJpaRepositories 어노테이션을 이용해서 리포지토리 객체들을 스프링이 직접 생성해서 제공한다.
스프링이 이 어노테이션을 발견하면 자동으로 우리가 정의한 모든 스프링 데이터 리포지토리 인터페이스의 구현체를 제공한다.
메인 애플리케이션에서도 @EnabledJpaRepositories 어노테이션을 붙일 수 있다.
테스트 애플리케이션을 실행할 때도 실질적으로 필요 없는 JPA 리포지토리를 활성화할 것이다.
'기능 어노테이션' -> 별도의 설정'모듈' 옮기는 것이 유연하게 만든다.
비슷한 방법으로 웹 어댑터, 애플리케이션 계층의 특정모듈을 위한 설정클래스를 만들 수 있다.
특정 모듈만 포함하고 그외는 모킹해서 애플리케이션 컨텍스트를 만들 수 있다. 이는 테스트에 큰 유연성이 생기게한다.
이 방식은 클래스패스 스캐닝 방식과 달리 @Component 어노테이션을 강제하지 않는다.
애플리케이션 계층을 프레임워크에 대한 의존성 없이 만든다.
이 점만 봐선 클린 아키텍처 강경파가 좋아하겠다.
이 방법에도 단점이 있다. 설정 클래스와 생성하려는 빈이 같은 패키지에 존재하지 않는다면 public 접근 지정자로 작성해야한다.
패키지를 모듈의 경계로 사용하고 패키지별로 설정 클래스를 만들 수 있다. 하지만 이러면 하위 패키지를 사용할 수 없다.
유지보수 가능한 소프트웨어를 만드는 데 어떻게 도움이 될까?
프레임워크는 개발을 편하게 만들어주는 다양한 기능을 제공한다.
그중 하나가 애플리케이션 조립하는 것이다.
클래스패스 스캐닝은 장점
- 전체를 고민하지 않고도 빠르게 개발할 수 있는 아주 편리한 기능이다.
- 상위 패키지, @Component 어노테이션만 있으면 찾은 클래스로 애플리케이션을 조립한다.
단점
- 규모가 커지면 투명성이 낮아진다. 어떤 빈이 애플리케이션 컨텍스트에 올라오는지 정확하게 알 수 없게 된다.
- 애플리케이션 컨텍스트의 일부만 독립적으로 띄우기가 어렵다.
반면,
애플리케이션 조립을 책임지는 전용 설정 컴포넌트를 만들면 애플리케이션이 이러한 책임으로부터 자유로워진다.
설정 컴포넌트는 프로젝트 설정에 따라서 파라미터들이 동적으로 변경하도록 구성이 가능(Spring Cloud Config Server)해서 인가..?
이 방식을 이용하면 다른 모듈로부터 고립되어 응집도가 매우 높은 모듈을 만들 수 있다.
그에 따라서 설정 컴포턴트를 유지보수하는데 약간의 시간을 추가로 들여야 한다.
'책 스터디 > [완료] 만들면서 배우는 클린아키텍처' 카테고리의 다른 글
만들면서 배우는 클린 아키텍처 - 11 정리 (0) | 2022.04.20 |
---|---|
만들면서 배우는 클린 아키텍처 - 10 정리 (0) | 2022.04.19 |
만들면서 배우는 클린 아키텍처 - 08 정리 (0) | 2022.04.15 |
만들면서 배우는 클린아키텍처 - 07 정리 (0) | 2022.04.14 |
만들면서 배우는 클린 아키텍처 - 06 정리 (0) | 2022.04.13 |