동일한
생명주기를 갖는도메인2개를 분리하고 작업 시Sevice Layer에서 2개의Repository를의존해야하는 단점이 존재
。Order와OrderItem도메인의 경우, 서로 공통된생명주기를 가져서비스 계층에서 서로 참조해야하는빈도가 많다.
▶ 다음처럼OrderService에서 2개의Repository를의존해야하는 상황이 발생.@Service @RequiredArgsConstructor public class OrderService { private final DataJpaOrdersRepository dataJpaOrdersRepository; private final DataJpaOrderItemRepository dataJpaOrderItemRepository; // }▶ 해결방법의 경우
파사드 패턴/Boundary Context를 구성한 해결방법이 존재하며, 본 글에서는Boundary Context를 통한 해결을 수행
Order Repository와OrderItem Repository를Boundary Context로서 병합한 단일Repository를의존하도록 설정
。Order과OrderItem을 둘다Boundary Context로 취급하여Repository Layer에서 하나의도메인으로병합
。Service Layer에서는Boundary Context로 구성된 하나의도메인을의존할 수 있는 장점이 존재.
▶ 한Service Layer에서 복수의Repository를의존하지 않아도 된다.
Boundary Context 인터페이스생성
。해당인터페이스내에는Order 도메인과OrderItem 도메인의Repository Layer관련기능을 공통적으로 작성
。각Order와OrderItem도메인에서JpaRepository<>를 확장한 기능을 가지므로,JpaRepository<>를 확장하지 않아도 된다.@Repository public interface OrderRepository { // Order 도메인 관련 Orders saveOrders(Orders order); // OrderItem 도메인 관련 OrderItem saveOrderItems(OrderItem orderItem); // ... }
Boundary Context 인터페이스 구현체생성
。각각의도메인의Repository를의존성 주입하여 각Repository의 기능을 사용하도록 설정
▶Order과OrderItem을 하나의Boundary Context로서 병합하여통합 관리
。BoundaryContext 구현체생성 시 해당구현체로의존성 주입@Repository @RequiredArgsConstructor public class DataJpaOrderRepositoryCombine implements OrderRepository { // private final DataJpaOrdersRepository dataJpaOrdersRepository; private final DataJpaOrderItemRepository dataJpaOrderItemRepository; // Order 도메인 관련 @Override public Orders saveOrders(Orders order) { return dataJpaOrdersRepository.save(order); } // OrderItem 도메인 관련 @Override public OrderItem saveOrderItems(OrderItem orderItem){ return dataJpaOrderItemRepository.save(orderItem); } }
Service Layer작성
。오직 하나의Boundary Context 역할의Repository에의존하면서Order 도메인과OrderItem 도메인의Repository 기능을 사용@Service @Transactional(readOnly = true) @RequiredArgsConstructor public class OrderService { private final OrderRepository orderRepository; // @Transactional(readOnly = false) public Orders saveOrders(Orders orders){ return orderRepository.saveOrders(orders); } // @Transactional(readOnly = false) public OrderItem saveOrderItems(OrderItem orderitem){ return orderRepository.saveOrderItems(orderitem); } }
QueryDSL활용 시Repository Layer의도메인 묶기
。Order과OrderItem을병합한Boundary Context의도메인을 정의한Repository에 추가로동적쿼리를 작성하기 위한JPAQueryFactory를의존성 주입하여 활용
。QueryDSL은JPQL을 생성하는 기능으로서읽기 작업에서만 유용
▶쓰기 작업의 구현을 위해EntityManager 객체를 추가하여 한Repository에읽기 / 쓰기 작업을 모두 구현.
。이를 통해Service Layer는 해당Repository만의존하면서Order/OrderItem관련DAO 로직과QueryDSL을 사용 가능
인터페이스정의public interface QueryDslOrderRepository{ Orders savedOrder(Orders order); OrderItem saveOrderItem(OrderItem orderItem); List<Orders> findAllOrderByQueryDsl(); }
구현체정의@Slf4j @Repository @Transactional(readOnly = true) @RequiredArgsConstructor public class QueryDslOrderRepositoryImpl implements QueryDslOrderRepository{ // private final JPAQueryFactory queryFactory; // private DataJpaOrdersRepository dataJpaOrdersRepository; private DataJpaOrderItemRepository dataJpaOrderItemRepository; // private final QOrders qOrders = QOrders.orders; private final QOrderItem qOrderItem = QOrderItem.orderItem; // @Transactional(readOnly = false) public Orders savedOrder(Orders order){ return dataJpaOrdersRepository.save(order); } // @Transactional(readOnly = false) public OrderItem saveOrderItem(OrderItem orderItem){ return dataJpaOrderItemRepository.save(orderItem); } public List<Orders> findAllOrderByQueryDsl(){ return queryFactory.selectFrom(qOrders) .fetch(); } }▶
조회 작업의 경우JPQL / QueryDSL로 구현하고,쓰기 작업의 경우 다른Repository에서JpaRepository에서 기본 구현된EntityManager를 활용한삽입 / 수정 / 삭제 메서드를 활용