Skip to the content.

Adapter Layer 통합 가이드

목적: Persistence Layer Adapter 전략 및 패키지 구조 개요


1️⃣ Adapter 전략 개요

CQRS 기반 Adapter 분리

┌─────────────────────────────────────────────────────────────────┐
│                       Adapter Layer                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │                    Command Adapter                         │ │
│  ├───────────────────────────────────────────────────────────┤ │
│  │  CommandAdapter                                           │ │
│  │  └─ JpaRepository (1:1) + Mapper                         │ │
│  │  └─ save(), delete(), deleteById()                       │ │
│  └───────────────────────────────────────────────────────────┘ │
│                                                                 │
│  ┌───────────────────────────────────────────────────────────┐ │
│  │                     Query Adapters                         │ │
│  ├───────────────────────────────────────────────────────────┤ │
│  │                                                           │ │
│  │  ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │
│  │  │  QueryAdapter   │ │AdminQueryAdapter│ │LockQueryAdp │ │ │
│  │  │   (일반 조회)   │ │   (관리자)      │ │   (Lock)    │ │ │
│  │  ├─────────────────┤ ├─────────────────┤ ├─────────────┤ │ │
│  │  │• QueryDslRepo   │ │• AdminQueryDsl  │ │• LockRepo   │ │ │
│  │  │• 4개 메서드     │ │• Join 허용      │ │• 6개 메서드 │ │ │
│  │  │• Domain 반환    │ │• DTO Projection │ │• Domain 반환│ │ │
│  │  └─────────────────┘ └─────────────────┘ └─────────────┘ │ │
│  │                                                           │ │
│  └───────────────────────────────────────────────────────────┘ │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2️⃣ 핵심 원칙: 1:1 매핑

Adapter = Repository + Mapper (필드 2개만)

┌─────────────────────────────────────────────────────────────────┐
│                    1:1 매핑 원칙                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  Adapter                           Repository                   │
│  ─────────                         ──────────                   │
│                                                                 │
│  ┌─────────────────┐      1:1      ┌─────────────────┐         │
│  │ CommandAdapter  │◄────────────►│  JpaRepository  │         │
│  │ • repository    │               │ • save()        │         │
│  │ • mapper        │               │ • delete()      │         │
│  └─────────────────┘               └─────────────────┘         │
│                                                                 │
│  ┌─────────────────┐      1:1      ┌─────────────────┐         │
│  │  QueryAdapter   │◄────────────►│ QueryDslRepo    │         │
│  │ • repository    │               │ • findById()    │         │
│  │ • mapper        │               │ • findByCriteria│         │
│  └─────────────────┘               └─────────────────┘         │
│                                                                 │
│  ┌─────────────────┐      1:1      ┌─────────────────┐         │
│  │AdminQueryAdapter│◄────────────►│AdminQueryDslRepo│         │
│  │ • repository    │               │ • Join 허용     │         │
│  │ (mapper 선택적) │               │ • DTO Projection│         │
│  └─────────────────┘               └─────────────────┘         │
│                                                                 │
│  ┌─────────────────┐      1:1      ┌─────────────────┐         │
│  │LockQueryAdapter │◄────────────►│  LockRepository │         │
│  │ • repository    │               │ • ForUpdate     │         │
│  │ • mapper        │               │ • ForShare      │         │
│  └─────────────────┘               └─────────────────┘         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

핵심 규칙:


3️⃣ Adapter 유형별 역할

Adapter 역할 Repository 반환 타입
CommandAdapter Command (CUD) JpaRepository Domain
QueryAdapter Query (일반 조회) QueryDslRepository Domain
AdminQueryAdapter Query (관리자) AdminQueryDslRepository DTO
LockQueryAdapter Lock (동시성) LockRepository Domain

상세 비교

항목 CommandAdapter QueryAdapter AdminQueryAdapter LockQueryAdapter
메서드 수 3개 4개 자유 6개
Mapper 필수 필수 선택적 필수
Join N/A ❌ 금지 ✅ 허용 N/A
반환 Domain Domain DTO Domain

4️⃣ N+1 해결 전략

Application Layer에서 해결

❌ Adapter에서 N+1 해결 (금지)
──────────────────────────────
OrderQueryAdapter
├─ orderQueryDslRepository
├─ customerQueryDslRepository  ← 금지! 1:1 위반
└─ mapper


✅ Application Layer에서 해결 (권장)
──────────────────────────────
OrderQueryUseCase (Application Layer)
├─ orderQueryPort.findByCriteria()     → 주문 목록
├─ customerQueryPort.findByIds()       → 고객 목록 (IN 절)
└─ 조합 및 Response 생성               → Application에서 처리

N+1 해결 패턴:

// Application Layer (UseCase)
@Component
public class OrderQueryUseCase {
    private final OrderQueryPort orderQueryPort;
    private final CustomerQueryPort customerQueryPort;

    @Transactional(readOnly = true)
    public List<OrderWithCustomerResponse> findOrdersWithCustomer(
            OrderSearchCriteria criteria) {
        // 1. 주문 조회
        List<Order> orders = orderQueryPort.findByCriteria(criteria);

        // 2. 고객 ID 수집
        Set<Long> customerIds = orders.stream()
            .map(Order::getCustomerId)
            .collect(Collectors.toSet());

        // 3. 고객 일괄 조회 (IN 절로 N+1 해결)
        Map<Long, Customer> customerMap = customerQueryPort.findByIds(customerIds)
            .stream()
            .collect(Collectors.toMap(c -> c.getId().getValue(), c -> c));

        // 4. 조합
        return orders.stream()
            .map(order -> new OrderWithCustomerResponse(
                order,
                customerMap.get(order.getCustomerId())
            ))
            .toList();
    }
}

5️⃣ 디렉토리 구조

소스 코드 구조

adapter-out/persistence-mysql/
└─ src/main/java/
   └─ com/company/adapter/out/persistence/
       └─ order/
           ├─ entity/
           │  └─ OrderJpaEntity.java
           │
           ├─ repository/
           │  ├─ OrderRepository.java              (JPA - Command)
           │  ├─ OrderQueryDslRepository.java      (QueryDSL - 일반)
           │  ├─ OrderAdminQueryDslRepository.java (QueryDSL - 관리자)
           │  └─ OrderLockRepository.java          (Lock)
           │
           ├─ mapper/
           │  └─ OrderJpaEntityMapper.java
           │
           └─ adapter/
              ├─ OrderCommandAdapter.java          (JpaRepository 1:1)
              ├─ OrderQueryAdapter.java            (QueryDslRepository 1:1)
              ├─ OrderAdminQueryAdapter.java       (AdminQueryDslRepository 1:1)
              └─ OrderLockQueryAdapter.java        (LockRepository 1:1)

문서 구조

adapter/
├── adapter-guide.md              ← 현재 문서 (통합 개요)
│
├── command/
│   ├── command-adapter-guide.md
│   ├── command-adapter-test-guide.md
│   └── command-adapter-archunit.md
│
└── query/
    ├── general/                  ← 일반 QueryAdapter
    │   ├── query-adapter-guide.md
    │   ├── query-adapter-test-guide.md
    │   ├── query-adapter-integration-testing.md
    │   └── query-adapter-archunit.md
    │
    ├── admin/                    ← 관리자 AdminQueryAdapter
    │   ├── admin-query-adapter-guide.md
    │   └── admin-query-adapter-archunit.md
    │
    └── lock/                     ← LockQueryAdapter
        ├── lock-query-adapter-guide.md
        ├── lock-query-adapter-test-guide.md
        └── lock-query-adapter-archunit.md

6️⃣ 사용 기준

어떤 Adapter를 사용할까?

┌─────────────────────────────────────────────────────────────┐
│                    Adapter 선택 가이드                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  저장/삭제 필요?                                            │
│  ├─ Yes → CommandAdapter                                   │
│  │                                                          │
│  조회 필요?                                                 │
│  ├─ 단순 조회 (ID, 목록) → QueryAdapter                    │
│  ├─ 관리자 복잡 조회 (Join) → AdminQueryAdapter            │
│  │                                                          │
│  동시성 제어 필요?                                          │
│  └─ 재고, 포인트, 좌석 예약 → LockQueryAdapter             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

케이스별 권장 조합

케이스 Adapter 조합
일반 CRUD CommandAdapter + QueryAdapter
관리자 목록 AdminQueryAdapter
재고 차감 LockQueryAdapter + CommandAdapter
복합 조회 Application Layer에서 여러 QueryPort 조합

7️⃣ 체크리스트

Adapter 설계 시:

1:1 매핑 원칙

역할 분리

네이밍 규칙

금지 사항


8️⃣ 참고 문서

Command Adapter

Query Adapter (일반)

Admin Query Adapter

Lock Query Adapter

Repository Layer


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