인프런 커뮤니티 질문&답변
Repository Adapter 설계에 대해 피드백을 부탁드립니다
작성
·
55
·
수정됨
0
안녕하세요 토비님!!
강의를 완강하고 제 프로젝트를 리팩토링하면서 피드백받고싶은점이 생겨 질문글을 올립니다.
Repository Port를 기술에 종속시키지 않기 위해 Adapter에서 JPA, MyBatis, QueryDSL을 조합하는 구조를 선택했는데, 이 설계 방향이 적절한지 조언을 구하고 싶습니다.
현재 구조
core/
└── domain/
└── application/
└── required/ ← Port 인터페이스
└── ~Repository
adapter/
└── persistence/
├── ~RepositoryAdapter ← Port 구현체
├── ~JpaRepository ← Spring Data JPA
└── ~MybatisMapper ← Mybatis 매퍼 인터페이스
의존 관계
[Service] → [Repository] ←impl― [RepositoryAdapter] → [JpaRepository]
→ [MybatisMapper]
→ [JPAQueryFactory]
(core) (core) (adapter) (adapter)
예시
@Repository
@RequiredArgsConstructor
public class ItemRepositoryAdapter implements ItemRepository {
private final ItemJpaRepository itemJpaRepository;
private final ItemMybatisMapper itemMybatisMapper;
@Override
public List<Item> findBySearchRequest(ItemSearchRequest request) {
return itemMybatisMapper.findBySearchRequest(request)
.stream()
.map(Item::from)
.toList();
}
@Override
public List<Item> saveAll(List<Item> items) {
return itemJpaRepository.saveAll(items);
}
}
@Repository
@RequiredArgsConstructor
public class AuctionRepositoryAdapter implements AuctionRepository {
private final JPAQueryFactory jpaQueryFactory;
private final AuctionJpaRepository auctionJpaRepository;
@Override
public void deleteAllByRegionAndRealmId(RegionType region, Long realmId) {
QAuction qAuction = QAuction.auction;
BooleanBuilder filter = new BooleanBuilder();
filter.and(qAuction.region.eq(region));
if (realmId == null) {
filter.and(qAuction.realmId.isNull());
} else {
filter.and(qAuction.realmId.eq(realmId));
}
jpaQueryFactory.delete(qAuction)
.where(filter)
.execute();
}
@Override
public int saveAll(List<Auction> auctions) {
if (auctions.isEmpty()) return 0;
return auctionJpaRepository.saveAll(auctions).size();
}
}
이 구조를 선택한 이유
동적 쿼리, 벌크 연산 등 JPA만으로 해결하기 어려운 케이스가 있어 MyBatis와 QueryDSL을 병행 사용하고 있습니다.
일반적인 방식인 Port 인터페이스가 Spring Data JPA Repository를 상속하는 구조를 채택하지 않은 이유:
MyBatis나 QueryDSL 기반 구현체를 만들 수 없음
CustomRepository 인터페이스를 별도로 만들어야 하는 복잡도 증가
현재 방식의 장점:
Port 인터페이스가 순수 Java 인터페이스로 유지됨
RepositoryAdapter에서 상황에 맞는 기술을 자유롭게 조합 가능
추가 인터페이스 없이 단순한 구조 유지
저는 현재 구조가 의존 관계도 외부에서 내부로 향하고 테스트도 쉬워서 괜찮다고 생각하는데 토비님의 생각도 듣고싶습니다!! 좋은 강의 감사합니다!!
답변 2
0
안녕하세요.
지금 접근하시는 방법이 어댑터를 사용해서 구현 부분을 분리하기에 적절한 경우입니다. 이렇게 기술을 병합해서 사용할 경우 리포지토리 인터페이스를 구현하는 업댑터를 따로 만드시는 방법을 사용하실 수 있습니다.
다만, Spring Data JPA를 사용했을 때의 장점을 놓칠 수도 있겠죠.
그런데 저라면 이렇게 해보겠습니다.
먼저 기본 Spring Data JPA Repository는 그대로 사용합니다. 복잡한 동적 쿼리나 네이티브 쿼리를 사용할 필요가 없는 Spring Data에서 만들어주는 JPQL로도 충분한 경우는 그 인터페이스의 리포지토리에서 처리합니다.
그러면서 동시에 CustomRepository 인터페이스를 추가로 만듭니다. 이건 Spring Data JPA 리포지토리가 아니기 때문에 구현 클래스를 따로 구현합니다. 그리고 이걸 결합해서 최종 인터페이스를 만드는 것이죠. Repository Composition 방법을 사용하는 것입니다.
이렇게 하면 QueryDSL이나 혹은 MyBatis 등의 구현 코드가 필요한 리포지토리를 편하게 조합해서 사용할 수 있습니다. Jpa Repository까지 굳이 어댑터를 태울 필요는 없죠.
다음 내용을 참고해보세요.
https://docs.spring.io/spring-data/jpa/reference/repositories/custom-implementations.html
0
안녕하세요, 인프런 AI 인턴입니다.
Reverie님, 현재 설계하신 Repository Adapter 구조는 포트-어댑터 패턴을 잘 활용하신 것 같습니다. 특히 기술 종속성을 줄이고, 다양한 기술 스택을 조합하여 사용하기 위한 전략은 강력하고 유연한 구조를 만들어낼 수 있습니다. 아래는 이 설계에 대한 추가적인 피드백입니다.
유연성: JPA, MyBatis, QueryDSL을 모두 활용할 수 있어 동적 쿼리나 벌크 연산과 같은 JPA만으로 처리하기 어려운 부분들을 커버할 수 있다는 점에서 이점이 큽니다.
기술 종속성 감소: Port 인터페이스를 순수 Java 인터페이스로 유지함으로써 특정 기술 스택에 종속되지 않게 설계한 점은 좋은 접근입니다. 이를 통해 테스트 작업에서도 다른 모듈의 영향 없이 독립적인 단위 테스트가 가능할 것입니다.
코드의 간결함: CustomRepository 인터페이스를 별도로 만들지 않음으로써 코드의 복잡도를 줄이고, 가독성을 향상시키는 점에서 좋은 방향성을 가진 것으로 보입니다.
명확한 구조: 의존 관계가 adapter에서 core로 흐르는 구조는 비즈니스 로직의 독립성을 유지시켜주며, 이는 애플리케이션의 유지보수에 큰 도움이 됩니다.
다만, 동시에 고려할 점도 있습니다:
복잡성 관리: Multi-데이터소스 조합이 주는 복잡성은 프로젝트의 규모가 커질수록 관리가 필요할 수 있습니다. 이를 위해 코드의 일관성을 유지하고 적절한 구현체를 선택할 수 있도록 코드 리뷰와 테스트를 강화하는 것이 중요합니다.
응집도: RepositoryAdapter가 여러 기술을 조합하는 만큼 응집도를 유지할 필요가 있습니다. 이는 구현체가 지나치게 비대해지는 것을 방지해야 한다는 점에서 중요한 포인트입니다.
전반적으로, 현재 구조는 적절한 상황에서 응용하기 좋은 패턴을 기초로 하고 있으므로, 지금의 방식을 유지하시면서, 필요에 따라 Continuous Integration 및 Testing을 강화하는 방향으로 가신다면 좋은 결과를 얻을 수 있을 것 같습니다.
좋은 질문 감사드리며, 프로젝트에 많은 성과가 있으시길 바랍니다! 🧑💻




