해당 포스트는 인프런의 Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 강의의 도움을 받았습니다
주문 시스템을 만들어야 한다면 무엇부터 시작할 것 같나요?
JPA Entity 먼저 떠올리셨다면, 계층형 아키텍처를 사용하고 있는 것이며 이는 데이터베이스 주도 설계로 이어지게 됩니다.
위와 같은 계층형 아키텍처에는 문제점이 있습니다.
데이터베이스의 변화에 유연하게 대응하지 못합니다.
Service가 JPA를 직접 사용하고 있기 때문에 Mongo DB와 같은 Document DB로 변경이 어렵습니다.
서비스가 사실상 모든 일을 전부 처리하는 신과 같은 존재가 되어 버립니다.
서비스가 가지고 있는 책임이 커집니다.
도메인의 존재가 숨겨져 명시적이지 않습니다.
동시 작업이 불가능합니다.
규모가 커질수록 확장성이 떨어집니다.
절차지향적 사고를 유도하여 낮은 Testability & BAD SOLID로 이어지게 됩니다.
도메인을 살려 줍니다.
도메인 엔티티와 영속성 객체를 구분할 수 있게 됩니다.
Service는 도메인을 Repository에서 가져와 일을 시키는 역할만 하게 됩니다.
도메인은 계층 간 연결된 의존성이 없습니다.
나가는 화살표가 없다고 생각하면 됩니다.
그러니 계층 간 의존성을 Mocking할 필요도 없습니다.
객체 생성이 쉽고, Testability가 높습니다.
Service가 Domain, JPARepository 둘에 의존하고 있으므로 의존성 역전을 활용하여 의존성을 약화시켜 봅시다.
public interface PostRepository extends JpaRepository<PostEntity, Long> {
@Modifying
@Transactional
@Query("DELETE FROM post p WHERE p.id IN :ids")
void deleteByIdIn(List<Long> p.deleteFlag = "fasle")
이렇게 구성하게 되면, Test 시에는 다음과 같이 Fake를 활용할 수 있습니다.
h2가 필요 없는 소형 테스트를 만들 수 있는 것입니다!
interface PostRepository {
Post getById(long id);
Optional<Post> findById(long id);
List<Post> findAll();
void deleteByIdIn(List<long> ids);
}
public PostRepositoryImpl implements PostRepository {
private final PostJpaRepository postJpaRepository;
@Override
Post getById(long id) {...}
@Override
Optional<Post> findById(long id){...}
@Override
List<Post> findAll(){...}
@Override
void deleteByIdIn(List<long> ids){...}
}
Service에서는 PostRepository만 참조하고 있으므로 테스트가 가벼워집니다.
Controller도 마찬가지로 의존성 역전을 적용해 줍니다.
테스트 할 때는 이것만 생각하면 됩니다.
기존 레이어드 아키텍처는 도메인이 숨겨져 있습니다.
도메인을 밖으로 빼내어 디렉토리를 구성합니다.
이것을 도식화하면 위와 같다고 볼 수 있으며, 패키지 간 의존성까지 표시하면 다음과 같습니다.
패키지 간 순환참조가 발생하지 않도록 주의해야 합니다.
디렉토리 구성 시 도메인을 밖으로 빼기 위해서 서비스에 필요한 도메인이 무엇인지 먼저 생각한 뒤에 구성을 해야 하는데..
앞에서 JPA Entity 먼저 생각하면 데이터베이스 중심적 사고를 하게 되어 안 좋다고 하지 않았나?? 이것과 충돌되는 것 같다는 생각이 드는데 어떻게 이해해야 할지 어렵네요~
정리 잘 해두었네용 잘보고 감니다