🌱 들어가기 전
이번 포스팅에서는 레이어 아키텍처의 문제점을 알아보고, SOLID 준수하고 Testability를 높이는 방향으로의 아키텍처 개선 과정을 살펴보자.
- 김우근님의 'Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트'를 공부하고 정리한 글입니다. ✏️
지난 포스팅과 이어집니다 !
https://deeper-dev.tistory.com/15
[테스트 코드와 설계] 프로젝트 Test Code 작성과 문제 해결
🌱 들어가기 전이번 포스팅에서는 프로젝트 코드에 대해 테스트 코드를 작성하는 과정을 살펴보자.- 김우근님의 'Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트'를 공부하고 정리한 글
deeper-dev.tistory.com
모든 Test가 h2를 필요로 한다는건,
1. 설계 이상
2. 필요한 테스트가 아닐 확률
왜 문제있는 테스트를 짜게 되었을까? 레이어드 아키텍처 때문이다.
🌱 레이어드 아키텍처
유사한 기능들을 같은 계층으로 묶어 관리하는 방식의 아키텍처 구조
특징
의존성 역전이나 추상화없이 바로 구현체를 사용한다.
💭 단점
1. DB 주도 설계를 유도한다.
개발자들이 제일 아랫 단계인 영속성 레이어를 어떻게 만들지 제일 먼저 고민하게 만든다.
모든 것이 영속성 계층을 토대로 만들어진다.
생각해보자. 어떤 시스템 만들어야 할 때, 보통 JPA Entity먼저 만들지 않았나?
- 하지만, 사실 시스템에 필요한 Use Case를 먼저 파악해야한다. ex) 주문시스템: 주문이 되어야하고, 주문내역 확인이 가능해야 하고 ...
- 그리고 이를 처리하기 위한 도메인과 도메인들의 관계를 생각하는게 먼저여야 한다.
2. 동시 작업이 불가능하다.
모든 것이 영속성 계층 위에 만들어지기 때문에 영속성 계층을 먼저 개발해야 하고, 그 다음 도메인 계층, 그 다음 웹 계층을 만들어야 한다.
따라서 특정 기능은 동시에 한 명의 개발자만 작업 가능하다.
(절차지향적인 코드를 짤 때의 문제이기도 하다.)
3. 도메인이 죽는다.
업무 도메인에 대해 아무것도 말해주지 않는다.
(+도메인: 비즈니스 문제를 해결하는 객체)
아래 구조를 보고, 이게 무슨 시스템인지, 도메인이 어디에 위치해야 하는지 파악이 불가능하다.
즉, 도메인이 눈에 들어오지 않아서 객체에 대해 진지한 고민을 하지 않게된다. 모든 객체는 getter, setter를 가지고 있고, 수동적으로 돌아갈 확률이 높다.
객체는 수동적이고, 전체 코드가 함수 위주로 돌아간다.
→ 절차지향적인 코드가 나온다.
Service단이 모든 일을 다 처리하는 신과 같은 존재가 되는 것이다.
4. 의존성에 대한 고민을 유도하지 않는다.
5. 규모가 커질수록 확장성이 떨어진다.
결론
- 절차지향적 사고를 유도한다.
- Testability가 낮고, SOLID를 지키지 않게된다.
🌱 개선된 아키텍처
💭 죽은 도메인 개선
도메인을 살리기 위해, 도메인 레이어를 생성한다.
Domain은 기존에 Service가 하던 역할을 한다. 즉, 실제 업무는 Domain 영역에서 이루어진다.
Service
Domain을 Repository에서 가져와 Domain에게 책임을 위임하는 곳이다.
Domain
OOP스러운 도메인들이 협력하는 곳이다.
lombok을 제외한 어노테이션이 없는 오브젝트이다. 즉, 도메인 엔티티와 영속성 객체랑 구분하겠다는 것이다!
*이렇게 레이어를 개선하면, SOLID를 지키지 않는 문제와 동시 작업이 불가능한 문제도 개선된 것이다.
💭 낮은 Testability 개선
이 아키텍처를 다시보자.
Domain
계층간 연결된 의존성이 없다.
도메인 레이어를 테스트한다고 상상해보자. 도메인 입장에서는 계층을 넘나드는 의존을 하지 않는다. 따라서 Mocking이 필요없다. 또한 도메인은 순수 java코드이기 때문에 인스턴스로 만들기 쉽다. 따라서 Testability가 높다.
JpaRepository
계층간 연결된 의존성이 없다. 따라서 Mocking도 필요가 없다.
🤔 개선된 아키텍처는 Service가 JpaRepository에서 Domain을 가져온다고 했다. 그럼 JpaRepository가 Domain을 의존하는게 아닌가?
💡 아니다. 아키텍처를 개선할 때 Domain레이어를 만들면서 도메인 엔티티와 영속성 객체를 구분시킨다. 따라서 JpaRepository는 영속성 객체를 의존하지, Domain을 의존하지는 않는다.
( 'JpaRepository 는 이미 JPA측에서 잘 테스트하고 있을텐데, 이를 굳이 테스트 해야하나?' 라는 의견도 있다고 한다.)
Service
2개의 의존성이 있다!
- Domain: 순수 java 코드라 인스턴스화가 어렵지 않다. Service를 테스트하는데 방해 요인이 아니다.
- JpaRepository: DB와 강결합 되어있는 JPA 코드를 인스턴스화 하는 것은 어렵다. 따라서 Service 테스트에 방해요인이기 때문에 해결해야 한다.
의존성으로 발생한 문제는 대부분 의존성 역전을 활용하면 해결된다!
의존성 역전을 활용해 Service단의 낮은 Testability를 개선해보자.
의존성 역전
비지니스 레이어에 직접 만든 Repository interface를 위치시킨다.
RepositoryImpl
영속성 객체를 가져온 후, 도메인 엔티티로 변환하여 return한다.
Repository
구현체는 관심이 없다. 어떤 질의와 명령을 할 수 있는지만 적혀있다.
Service는 Repository를 '질의했을 때 필요한 도메인을 내려주는 녀석' 정도로 알고있다.
이렇게 개선하면면 문제가 어떻게 해결될까?
테스트할 때, Fake를 사용함으로써 Testability를 높일 수 있게된다. 이렇게 함으로써 Service레이어는 영속성 계층과의 의존관계가 매우 약해진다.
이제 Service의 낮은 Testability는 해결되었다. 그 다음으로 Controller의 Testability를 보자.
Controller
3개의 의존성이 있다.
- Service
- Domain
- <<interface>> Repository
모두 인스턴스화는 쉽지만, Controller 테스트를 위해 3개의 의존성을 모두 준비하는 것은 시간낭비, 자원낭비 같다. 테스트도 복잡해질 것이다.
따라서 의존성 역전을 활용한다!
의존성 역전
Service interface를 웹 레이어에 위치시킨다.
이렇게 개선하면 문제가 어떻게 해결될까?
테스트할 때 Service에 대응되는 Mock이나 Fake를 Service 구현체로 두어서 테스트를 쉽게 만든다.
💭 개선된 아키텍처 (최종)
지금까지는 영속성 레이어에 Repository가 오는 것만 생각했지만, 사실 Repository뿐만 아니라 모든 외부 연동에서 이 원칙을 지킬 수 있다.
모든 외부연동이기 때문에, Persistence 레이어의 이름을 infrastructure로 수정하였다.
인터페이스와 구현체로 분리해서 의존성을 역전 시키자!
'Back-End > Test Code' 카테고리의 다른 글
[테스트 코드와 설계] 집중하는 테스트와 테스트 리팩토링 사항들 (0) | 2024.09.25 |
---|---|
[트러블 슈팅 - 테스트 코드] 테스트 데이터 삽입 시 발생하는 auto_increment 컬럼값 충돌 문제 해결 (0) | 2024.09.09 |
[트러블 슈팅 - 테스트 코드] 테스트 간 데이터 간섭과 트랜잭션 문제 해결 - @SpringBootTest 테스트 격리시키기 (2) | 2024.09.08 |
[테스트 코드와 설계] 프로젝트 Test Code 작성과 문제 해결 (0) | 2024.09.04 |
[테스트 코드와 설계] 빌더 패턴과 엔티티, 그리고 테스트에 대한 조언 (0) | 2024.09.04 |