JPA Repository 사용 시 계층간 의존관계 문제점

은찬·2023년 9월 26일
2

Java, Spring

목록 보기
1/7

JpaRepository 설계

보통 Repository 를 설계할 때 아래와 같은 그림으로 설계를 할 것이다.

위와 같은 구조는 Domain 에 Repository 인터페이스를 두고 Infrastructure 에서 해당 인터페이스를 구현하며 infra → domain 의 저수준 → 고수준 의 의존관계를 형성하여 DIP 를 준수하는 그림이다.

그럼 뭐가 문제일까...? 🤔

라고 생각할 수 있는데, 아래에서 문제가 되는 예시를 들겠다.

우리는 보통 JPA Repository 를 사용할 때 Data Jpa Repository 를 사용한다.
사용 방법은 간단하다.

public interface GoodRepository extends JpaRepository<Good, Long> {

}

이런 식으로 JpaRepository 를 상속한 인터페이스를 사용한면 된다.
그리고 해당 인터페이스를 Domain layer 에 두는데…. 여기서 바로 문제가 발생한다!

Data Jpa Repository 를 사용하려면 반드시 JpaRepository 를 상속해야한다. 그러면 위 코드에서 예를 들자면 GoodRepositoryJPA 라는 기술에 의존하게 되고 Domain layer 에서 의존관계가 출발하여 DIP 를 위반하는 것이다.

먄약 해당 부분에서 DIP 를 위반하게 된다면, Repository 구현체를 변경하게 된다면 도메인 코드의 수정이 필요한 상황이 생긴다.
만약 Jpa 에서 다른 구현체로 바뀐다면, 기존에 JPA 에서 제공받아서 직접 정의하지 않은 쿼리들은 사라지게 되며 많은 곳에서 빨간줄을 뱉어댈 것이다.

그래서 아래에 이를 해결할 방법을 제시해보겠다 🔍

해결 방안

우선 첫 번째 방안은 어뎁터 패턴을 적용하는 것이다.
코드로 예를 들겠다 💻

먼저 아래와 같은 퓨어 인터페이스 레포지토리를 만든다.

//Domain layer
public interface GoodRepository {

	Optional<Good> findByName(final String name);

	Good save(final Good good);
}

그리고 아래와 같은 Jpa Repository 를 Infrastructure layer 에 만든다.

//Infrastructure layer
public interface GoodJpaRepository extends JpaRepository<Good, Long> {

	Optional<Good> findByName(final String name);
}

그리고 이 둘을 이어줄 어댑터 클래스를 Infrastructure layer 에 만든다.

//Infrastructure layer
@Repository
@RequiredArgsConstructor
public class GoodRepositoryAdaptor implements GoodRepository{

	private final GoodJpaRepository goodJpaRepository;

	@Override
	public Optional<Good> findByName(final String name) {
		return goodJpaRepository.findByName(name);
	}

	@Override
	public Good save(final Good good) {
		return goodJpaRepository.save(good);
	}
}

이렇게 구현하면 GoodRepository 는 퓨어 인터페이스로 어디에도 의존하지 않는다. 그리고 GoodRepositoryAdaptorGoodRepository 를 구현하며 infra → domain 방향으로만 의존하게 되며 DIP 를 지키며 설계할 수 있다 👍

나의 더 간단한 해결방안

위와 같은 방법으로 해결한다면 깔끔하게 해결할 수 있지만, 생성되는 클래스가 너무 많아지고 그에 따라서 추가되는 테스트 코드 또한 리소스다…

그래서 나는 아래와 같은 방법을 생각했다 🔍

public interface GoodJpaRepository extends GoodRepository, JpaRepository<Good, Long> {

	Optional<Good> findByName(final String name);
}

이렇게 JpaRepository 에서 Domain layer 의 GoodRepository 를 상속하는 것이다.

이렇게 되면 GoodRepository 의 메소드를 GoodJpaRepository 가 이어받게 되고 Data Jpa(Hibernate)가 GoodRepository 의 메소드를 알아서 구현해준다.

결국 우리의 목적인 Domain layer 의 GoodRepository퓨어 인터페이스로 지킬 수 있고 의존관계 또한 올바르게 형성할 수 있다. 어댑터 클래스도 따로 구현안해도 된다.
쉽게 생각하면 GoodJpaRepository 가 구현체와 어댑터의 기능을 함께 한다고 생각하면 될 것 같다.

나는 이 방법이 유연하고 문제가 없다고 생각하지만, 여러 피드백을 받아봐야할 것 같다.

profile
`강한` 백엔드 개발자라고 해두겠습니다

3개의 댓글

comment-user-thumbnail
2024년 7월 11일

안녕하세요 좋은 글 감사합니다. 혹시 지금도 같은 생각을 하고 계신가요? 아니면 생각이 바뀌셨다면 그 이유가 궁금합니다

1개의 답글