이 시리즈는 TDD를 숙달하기 전에 TEST 자체에 대한 이해를 높이기 위한 학습 시리즈입니다
진단
h2
가 들어간 테스트는 앞선 분류에서 중형 테스트라고 분류된 테스트입니다
- 때문에 속도도 빠르지 않고, 이런 테스트가 양이 많아질수록 전체 테스트를 돌리는 시간에 대한 부담이 커질 것입니다
문제가 발생하는 이유
- 설계가 잘못되었을 확률
- 현재까지의 예제로 보면 현재 예제의 시스템은
RDB
에 강결합된 상태이고, h2
와 Mockito
가 없이는 테스트 작성이 어렵습니다
- 이 예제에서 사용하는
h2
는 테스트용 임베디드 모드를 지원했기에 테스트라도 진행할 수 있었지만, 만약 ElasticSearch
같이 지원이 안되는 DB
를 사용중일 경우는 어떻게 해야 할까요?
- 지금 작성한 테스트가 실제로 테스트가 필요한 본질이 아닐 확률
레이어드 아키텍처
- 위 문제들의 근본적인 원인은 예제 시스템의 아키텍처가
레이어드 아키텍처
로 이뤄져 있기 때문입니다
- 레이어드 아키텍처는 가시적으로 개발할 수 있고, 구조가 비슷한 기능끼리 묶여있어서 파악하기 쉽다는 장점을 가지고 있지만 단점들도 존재합니다
레이어드 아키텍처의 단점
DB 주도 설계
- 레이어드 아키텍처는 데이터베이스 주도 설계를 유도합니다
- 어떤 서비스를 만드려고 할 때, 이런 아키텍처에 익숙해진 상태라면 먼저
Entity
를 떠올리게 마련입니다
- 하지만, 어떤 서비스를 계획할 때 가장 먼저 생각해야 하는 건
Use case
이고, 여기 안의 도메인
들과 도메인들의 관계
를 먼저 떠올려야 합니다
동시 작업 문제
- 어떤 기능을 개발해야 한다고 가정했을 때,
Entity
와 Repository
가 나와야 Service
개발이 가능하고, Service
가 나와야 Controller
가 개발 가능합니다
- 따라서 특정 기능 개발을 한 명만 수행이 가능하게 됩니다
도메인이 죽는다
- 객체를 중심으로 설계하지 않고, 함수를 위주로 설계하게 되면서 절차 지향적인 코드가 나오게 됩니다
- 그만큼 점점 도메인에 대한 관심이 멀어지게 됩니다
- 사실상
Service
가 모든 일을 처리하는 역할을 수행하게 됩니다. 이런 서비스를 fat service
라고 부릅니다
- 이뿐만이 아니라 레이어드 아키텍처는 의존성에 대한 고민을 유도하지 않고, 규모가 커질수록 확장성이 떨어진다라는 단점 또한 가지고 있습니다
개선된 아키텍처
도메인
- 비즈니스 문제를 해결하는 객체
OOP
스러운 도메인들이 협력하는 곳
lombok
을 제외한 어노테이션이 없는 오브젝트로 도메인 엔티티
와 영속성 객체
를 구분합니다
- 계층간 연결된 의존성이 없습니다
Repository의 DB 강결합 구조 해결
Service
가 도메인
을 의존하고 있지만 도메인
은 순수 자바코드로 인스턴스화 하는 게 어렵지 않으므로, 테스트에 큰 방해요소가 아닙니다
- 비즈니스 레이어에 만든
Repository
인터페이스를 위치시킵니다
- 영속성 레이어에서는 위 인터페이스를 구현한 구현체를 두고, 이 구현체가
JpaRepository
를 사용하도록 합니다
- 위와 같은 간단한 구조 변경으로 테스트할 때 우리는
fakeRepository
를 구현체로 등록해 쉽게 테스트할 수 있는 환경이 됐습니다
Controller의 의존성 풀어주기
- 앞선
Service
의 Repository
에 대한 의존성을 풀어준 방법으로 Controller
가 Service
, Repository
, Domain
에 가지는 의존성을 해결해 줍니다
- 이렇게 구조 개선을 하면 마찬가지로 테스트할 때,
Service
를 대체하는 FakeService
나 MockService
를 구현체로 둠으로써 쉽게 테스트할 수 있습니다
외부 연동
- 위 구조 개선은 이 예제 시스템에서 보여지는 부분뿐만 아니라 외부 시스템을 연동해야 할 경우에도 모두 적용할 수 있는 아키텍처이고, 이를 적용하면 보다시피 외부 시스템일지라도 쉽게 테스트할 수 있는 환경을 가지게 됩니다
- 예제 시스템에서 봤던
JavaMailSender
역시 인터페이스와 구현체로 분리해서 작성할 수 있겠습니다
개선 전 추가될 사항
- 의존성 역전 원리를 이용하여 외부를 다루도록 개선할 겁니다
- 약해진 의존성의 이점인 테스트에 용이해진 설계에서 필요한 경우
Mock
객체로 치환해서 테스트할 겁니다(Mockito
, h2
없이 테스트를 짭니다)
- 패키지 구조 개선
-> 도메인 중점으로 패키지를 구성합니다
-> 이렇게 구성할 경우 추후 MSA 아키텍처를 적용하는데 유리합니다
- 패키지 이름
respository
대신 infrastructure
로 변경하겠습니다
- 외부 기능들도 포함하는 패키지이기 때문에 더 적절한 패키지명으로 변경합니다
- 순환 참조가 생기는지 의식하면서 개발해야 합니다
JPA 엔티티
와 도메인 모델
을 분리
Service
에 있는 setter
를 모두 제거하고 domain/VO
로 이동시킵니다
CQRS
- 명령과 질의의 책임을 분리
- 메소드를 명령과 질의로 나누자
- 명령: 상태를 바꾸는 메소드
- 명령 메소드는
void
타입이어야 합니다
- 편의상 명령 메소드가 종종
return this
하는 경우도 있는데, 이렇게 해서는 안됩니다
- 질의: 상태를 물어보는 메소드
- 하나의 메소드는 명령이나 쿼리여야 하며, 두 가지 기능을 모두 가져서는 안됩니다. 명령은 객체의 상태를 변경할 수 있지만, 값을 반환하지는 않는다. 쿼리는 값을 반환하지만 객체를 변경하지 않는다