Skip to the content.

AdminQueryAdapter 가이드

목적: 관리자 조회 전용 CQRS Query Adapter 구현 가이드 (1:1 매핑)


1️⃣ AdminQueryAdapter란?

역할

관리자 Query 요청 → AdminQueryDslRepository → DTO Projection → 반환

관리자 전용 복잡 조회를 담당하며, AdminQueryDslRepository에 1:1 매핑되어 위임합니다.

일반 QueryAdapter와 차이점

항목 QueryAdapter AdminQueryAdapter
Repository QueryDslRepository AdminQueryDslRepository
Join ❌ 금지 ✅ 허용
메서드 제한 4개 고정 ✅ 자유
반환 타입 Domain DTO Projection
Mapper 필수 (Entity→Domain) 선택적 (DTO 변환 시)

책임

핵심 원칙

Application Layer (UseCase/Facade)
  └─ Admin Query Port 호출
      └─ AdminQueryAdapter (1:1 매핑)
            └─ AdminQueryDslRepository
                  └─ DTO Projection (Join 허용)

2️⃣ 핵심 원칙

원칙 1: 1:1 매핑

원칙 2: DTO Projection 직접 반환

// ✅ DTO Projection 직접 반환 (Mapper 불필요)
@Component
public class OrderAdminQueryAdapter implements OrderAdminQueryPort {

    private final OrderAdminQueryDslRepository adminQueryDslRepository;

    @Override
    public List<AdminOrderListResponse> findOrderListWithMember(AdminOrderSearchCriteria criteria) {
        // DTO Projection 직접 반환
        return adminQueryDslRepository.findOrderListWithMember(criteria);
    }
}

원칙 3: 필요 시 Response 변환

// ✅ Response 변환이 필요한 경우 Mapper 사용
@Component
public class OrderAdminQueryAdapter implements OrderAdminQueryPort {

    private final OrderAdminQueryDslRepository adminQueryDslRepository;
    private final AdminOrderResponseMapper responseMapper;  // 선택적

    @Override
    public AdminOrderDetailResponse findOrderDetail(OrderId orderId) {
        AdminOrderProjection projection = adminQueryDslRepository.findOrderDetail(orderId.getValue());
        return responseMapper.toResponse(projection);  // DTO → Response 변환
    }
}

원칙 4: 비즈니스 로직 금지

// ❌ 비즈니스 로직 금지
public List<AdminOrderListResponse> findOrderList(AdminOrderSearchCriteria criteria) {
    List<AdminOrderListResponse> list = adminQueryDslRepository.findOrderList(criteria);

    // 금지! 비즈니스 로직
    if (criteria.getStatus() == OrderStatus.PENDING) {
        list = list.stream()
            .filter(o -> o.getAmount().compareTo(BigDecimal.ZERO) > 0)
            .toList();
    }
    return list;
}

// ✅ 단순 위임만
public List<AdminOrderListResponse> findOrderList(AdminOrderSearchCriteria criteria) {
    return adminQueryDslRepository.findOrderList(criteria);
}

3️⃣ 템플릿 코드

Port 인터페이스

public interface {Bc}AdminQueryPort {

    // 관리자 목록 조회 (Join 허용)
    List<Admin{Bc}ListResponse> findList(Admin{Bc}SearchCriteria criteria);

    // 관리자 상세 조회 (Join 허용)
    Optional<Admin{Bc}DetailResponse> findDetail({Bc}Id id);

    // 관리자 통계 조회
    Admin{Bc}StatsResponse getStats(Admin{Bc}StatsQuery query);

    // 관리자 페이징 조회
    Page<Admin{Bc}ListResponse> findPage(Admin{Bc}SearchCriteria criteria, Pageable pageable);
}

Adapter 구현 (DTO 직접 반환)

@Component
public class {Bc}AdminQueryAdapter implements {Bc}AdminQueryPort {

    private final {Bc}AdminQueryDslRepository adminQueryDslRepository;

    public {Bc}AdminQueryAdapter({Bc}AdminQueryDslRepository adminQueryDslRepository) {
        this.adminQueryDslRepository = adminQueryDslRepository;
    }

    /**
     * 관리자 목록 조회
     *
     * @param criteria 검색 조건
     * @return 관리자 목록 Response (DTO Projection)
     */
    @Override
    public List<Admin{Bc}ListResponse> findList(Admin{Bc}SearchCriteria criteria) {
        return adminQueryDslRepository.findList(criteria);
    }

    /**
     * 관리자 상세 조회
     *
     * @param id Bc ID
     * @return 관리자 상세 Response (DTO Projection)
     */
    @Override
    public Optional<Admin{Bc}DetailResponse> findDetail({Bc}Id id) {
        return adminQueryDslRepository.findDetail(id.getValue());
    }

    /**
     * 관리자 통계 조회
     *
     * @param query 통계 쿼리
     * @return 통계 Response
     */
    @Override
    public Admin{Bc}StatsResponse getStats(Admin{Bc}StatsQuery query) {
        return adminQueryDslRepository.getStats(query);
    }

    /**
     * 관리자 페이징 조회
     *
     * @param criteria 검색 조건
     * @param pageable 페이징 정보
     * @return 페이징된 목록
     */
    @Override
    public Page<Admin{Bc}ListResponse> findPage(Admin{Bc}SearchCriteria criteria, Pageable pageable) {
        return adminQueryDslRepository.findPage(criteria, pageable);
    }
}

Adapter 구현 (Response 변환 필요 시)

@Component
public class {Bc}AdminQueryAdapter implements {Bc}AdminQueryPort {

    private final {Bc}AdminQueryDslRepository adminQueryDslRepository;
    private final Admin{Bc}ResponseMapper responseMapper;

    public {Bc}AdminQueryAdapter(
        {Bc}AdminQueryDslRepository adminQueryDslRepository,
        Admin{Bc}ResponseMapper responseMapper
    ) {
        this.adminQueryDslRepository = adminQueryDslRepository;
        this.responseMapper = responseMapper;
    }

    @Override
    public List<Admin{Bc}ListResponse> findList(Admin{Bc}SearchCriteria criteria) {
        List<Admin{Bc}Projection> projections = adminQueryDslRepository.findList(criteria);
        return projections.stream()
            .map(responseMapper::toResponse)
            .toList();
    }
}

4️⃣ 체크리스트

AdminQueryAdapter 구현 시:

구조

반환 타입

금지 사항


5️⃣ 참고 문서


작성자: Development Team 최종 수정일: 2025-12-04 버전: 1.0.0