본문 바로가기
Project/Trouble Shooting

멀티 모듈에서 독립적으로 Repository 관리하기(Querydsl)

by yoon_seon 2024. 1. 17.

멀티모듈의 도입

사이드 프로젝트를 진행하면서 독립적인 배포와 높은 유지보수성을 목표로 하여 멀티모듈 구조를 도입했습니다.

각 모듈은 명확한 책임을 가지며, 필요에 따라 의존성을 설정하여 독립적으로 개발, 배포할 수 있도록 설계했습니다.

각 모듈의 대한 설명과 의존성은 아래의 그림과 같습니다.

  • module-api : 서비스 운영 서버를 독립적으로 관리하기 위한 모듈 domain 모듈에 의존
  • module-admin : 관리자 서버를 독립적으로 관리하기 위한 모듈로 domain 모듈에 의존
  • module-domain : 엔티티와 repository를 독립적으로 관리하기 위한 모듈

 

모듈간의 독립성 강화를 위한 팀 내 정책 수립

멀티 모듈 도입 시, 공통 의존 모듈인 common이나 domain에 특정 모듈에서 사용하는 코드가 늘어나면, 서로를 참조하며 변경 사항이 발생할 때 코드 수정이 번거로워질 수 있습니다.

이로 인해 아래의 그림과 같이 common에서 서로가 서로를 참조하며 코드가 의존성 덩어리로 연결되는 현상이 발생할 수 있습니다.

출처 : https://techblog.woowahan.com/2637/

 

이런 상황을 방지하고 모듈 간의 독립성을 유지하기 위해 팀 내에서는 각 모듈에서만 필요로 하는 Object는 해당 모듈 내에 위치하도록 정책을 수립했습니다.

 

정책에 따라 정의한 패키지 구조는 아래와 같습니다.

 

module-api

module-api은 해당 모듈 내에서만 사용되는 도메인별 controller, service, dto 패키지가 위치합니다.

 

module-domain

module-domain 에서는 엔티티와 JpaRepository를 상속받은 도메인의 repository 인터페이스가 위치합니다.


따라서 API 모듈에 위치한 도메인별 service 클래스에서 domain 모듈에 위치한 repository를 DI(의존성 주입) 받아 사용할 수 있고, domain 모듈을 의존하고 있는 다른 모듈들은 Entity와 Repository를 재사용할 수 있었습니다.

 

 

domain 모듈에 api 모듈에서만 사용하는 코드가 발생

module-api의 기능을 구현하면서 데이터베이스에 저장된 데이터를 추출해야 하는 경우, 해당 기능을 위해 module-domain에 위치한 Repository 인터페이스에 쿼리메서드를 추가하게 되었습니다.

 

module-domain의 Repository에 module-api에서만 사용할 목적의 쿼리메서드가 추가되면서, 각 모듈에서만 필요한 Object는 해당 모듈 내에 위치하고자 했던 팀 내 정책에 어긋나게 되었고, 모듈 간 독립성이 보장되지 않는 느낌을 받았습니다.

앞으로 기능을 구현하면서 특정 모듈에서 필요로 하는 쿼리메서드가 module-domain에 위치한 Repository에 계속 추가된다면 다른 모듈에서는 필요로 하지 않는 쿼리메서드로 인해 Repository가 난잡해질 것으로 예상되었습니다.

이러한 문제를 해결하기 위해 인터페이스 상속을 통한 방법을 선택했습니다.

 

domain 모듈 Repository 인터페이스 상속을 통해 해결

module-domain 모듈에 위치한 Repository는 쿼리 메서드 없이 JpaRepository를 상속받고, module-api에 Repository를 생성한 후 Domain에 위치한 Repository를 상속받도록 하였습니다.

module-api에 Repository를 추가함으로써 기존 module-domain의 repository와 인터페이스명이 중복되기 때문에 아래와 같이 네이밍을 수정하였습니다.

  • module-domain에 위치한 Repository : [엔티티명]DomainRepository
  • module-api 모듈에 위치한 Repository : [엔티티명]Repository

module-domain

  • JpaRepository를 상속

module-api

  • JpaRepository를 상속받은 DomainRepository를 상속

이로써 module-api에 생성한 Repository는 JpaRepository의 추상 메서드를 사용할 수 있으며, 쿼리 메서드가 필요한 경우 module-api에 생성한 Repository에 구현하면 되므로 다른 모듈에서 사용할 수 없어 독립성을 만족시킬 수 있었습니다.

 


Querydsl 도입

module-api에서 쿼리 메서드를 사용해서 추출할 수 없는 질의는 Querydsl을 사용하여 추출하게 되었습니다.

따라서 module-api의 build.gradle에 다음과 같이 라이브러리를 추가합니다.


기존에 있었던 모듈간 경계성이 모호해지는 문제를 각 모듈에서 module-domain의 Repository 인터페이스 상속을 통해 해결하였기 때문에 문제없이 Querydsl을 적용할 수 있습니다.

 

RepositoryCustom 인터페이스

 

RepositoryCustom를 implement한 RepositoryImpl 클래스

  • querydsl을 사용하여 질의를 작성합니다.


RepositoryCustom을 Repository에 상속

Repository는 DomainRepository와 RepositoryCustom을 다중상속 받아 부모 인터페이스의 추상클래스를 사용할 수 있게되었습니다.

 

따라서 module-api에 위치한 service에서는 아래와 같이 RepositoryImpl에서 querydsl을 사용하여 구현한 메서드를 사용할 수 있습니다.

 

만약 Querydsl을 사용하려고 하지만 module-domain의 Repository 인터페이스 상속받지 않았다면?

다시 처음으로 돌아가서, 만약 모듈간의 경계성이 무너지더라도 팀 내 정책이 변경되어 module-domain에만 Repository 인터페이스를 두고 전체 모듈의 쿼리메서드를 관리한다고 가정해봅시다.

만약 module-api에서 Querydsl을 필요로 한다면 module-domain에서 RepositoryCustom을 생성하고 RepositoryImpl을 구현한다면 module-api에 위치한 service에서 구현체를 가져다가 사용할 수 있습니다.

그러나, DTO 자체를 @QueryProjection을 적용하여 Q 타입으로 만들어야한다면?

  1. module-api에서 DTO를 가지고 있기 때문에 api에 DTO 클래스를 추가하고 @QueryProjection을 적용합니다.
  2. 따라서 DTO의 Q 타입 객체는 module-api에 생성되게 됩니다.
  3. module-domain에 위치한 RepositoryImpl에서 DTO Q 타입 객체를 사용하려면 module-domain에서 module-api를 import하기 위해 의존해야합니다.
  4. 이렇게 된다면 두 개의 모듈이 서로를 참조하게 되므로 멀티모듈 관계에서 '역 의존성' 문제가 발생할 수 있습니다.


그러면 DTO를 module-domain에 위치시키면 해결되지 않을까?

멀티 모듈에서 역 의존성 문제를 해결하기 위해 DTO를 module-domain에 위치시킨다면 module-api에서만 사용하는 코드가 module-domain에 추가되어 모듈의 독립성을 유지할 수 없게 되며 모듈 간의 결합도를 높이게 되어 유지보수 및 확장성에 문제를 야기할 수 있습니다.

또한, 더이상 module-domain이 엔티티와 repository를 독립적으로 관리하기 위한 모듈이었던 초기 의도와 상반되는 결과를 초래하게 됩니다.

 


멀티 모듈을 적용하면서..

멀티 모듈 설계의 핵심은 각 모듈이 특정 역할을 수행하며 필요한 로직을 자체적으로 관리해야 한다는 것 입니다. 이를 통해 모듈 간의 결합도를 낮추고 각 모듈이 독립적으로 개발, 테스트, 배포될 수 있도록 하는 것이 목표라고 생각합니다.

멀티 모듈을 설계하면서 가장 중요하다고 느낀것은 각 모듈에서만 사용하는 코드는 특정 모듈에 위치해야한다는 것 이었습니다. 이 정책이 지켜지지 않으면 결국 모듈 간의 경계가 모호해지고, 모듈의 역할이 불분명해지며 결국 멀티 모듈을 도입한 이유가 퇴색될 수 있습니다.(차라리 단일모듈을 사용하고 말지...)

따라서 팀 내에서 각 모듈의 독립성을 보장하고 각 모듈의 역할과 책임을 명확히 정의하여 모듈간의 결합도를 최소화 하는 정책을 수립하는 것이 중요하다고 생각됩니다.

댓글