FlightSearchInternalController DFS (기능명세서)

1. API 개요 및 목적

1.1 개요

항목
컨트롤러FlightSearchInternalController
소스 위치air-intl-search/src/main/kotlin/com/triple/air/intl/search/interfaces/controller/internal/FlightSearchInternalController.kt
Base Path/internals/flights
목적내부 서비스 간 국제선 항공권 검색 API 제공

1.2 목적 및 특징

FlightSearchInternalController내부 서비스(Internal Service) 전용 국제선 항공권 검색 API를 제공합니다.

주요 특징:

  • 외부 사용자가 아닌 내부 시스템 간 통신을 위한 엔드포인트
  • 일반 검색과 재발행(Reissue) 검색 기능 제공
  • 응답 형식이 InternalFlightItemView내부 시스템에 최적화
  • 추천(Recommendation) 기능 비활성화 (useRecommendation = false)
  • 그룹핑 기준이 FlightGroupCriteria.SCHEDULE로 고정

2. 엔드포인트별 상세 분석

2.1 엔드포인트 목록

HTTP Method경로메서드명설명
GET/internals/flights/search/{originType}:{origin}-{destinationType}:{destination}/{outboundDate}searchOneWayTripFlightsListKey편도 검색 시작
GET/internals/flights/search/{originType}:{origin}-{destinationType}:{destination}/{outboundDate}/{inboundDate}searchRoundTripFlightsListKey왕복 검색 시작
POST/internals/flights/{listKey}getFlights검색 결과 조회
GET/internals/flights/{listKey}/statusgetCacheKeyStatus캐시 키 상태 확인
POST/internals/flights/reissuereissueSearch재발행 검색 시작

2.2 편도 검색 (searchOneWayTripFlightsListKey)

소스 위치: FlightSearchInternalController.kt:34-63

기본 정보

항목
HTTP MethodGET
경로/internals/flights/search/{originType}:{origin}-{destinationType}:{destination}/{outboundDate}
응답 상태200 OK

Path Variables

파라미터타입필수설명
originTypeLocationTypeY출발지 타입 (CITY 또는 AIRPORT)
originStringY출발지 IATA 코드
destinationTypeLocationTypeY도착지 타입 (CITY 또는 AIRPORT)
destinationStringY도착지 IATA 코드
outboundDateLocalDateY출발일 (ISO 형식: yyyy-MM-dd)

Query Parameters

파라미터타입필수기본값설명
cabinsSet<CabinType>Y-좌석 등급 (ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST)
adultIntY-성인 수
childIntN0아동 수
infantIntN0유아 수
freeBaggageOnlyBooleanNfalse무료 수하물 포함 항공편만
useCacheBooleanNtrue상세 조회용 캐싱 사용 여부

응답

{
  "key": "UUID"
}

2.3 왕복 검색 (searchRoundTripFlightsListKey)

소스 위치: FlightSearchInternalController.kt:65-101

기본 정보

항목
HTTP MethodGET
경로/internals/flights/search/{originType}:{origin}-{destinationType}:{destination}/{outboundDate}/{inboundDate}
응답 상태200 OK

Path Variables

파라미터타입필수설명
originTypeLocationTypeY출발지 타입
originStringY출발지 IATA 코드
destinationTypeLocationTypeY도착지 타입
destinationStringY도착지 IATA 코드
outboundDateLocalDateY가는 날 (ISO 형식)
inboundDateLocalDateY오는 날 (ISO 형식)

Query Parameters

편도 검색과 동일

왕복 여정 생성 로직

소스 위치: FlightSearchInternalController.kt:90-100

OriginDestinationLocationInfo(
    origin = locationService.getLocationInfo(iata = origin, type = originType),
    destination = locationService.getLocationInfo(iata = destination, type = destinationType),
    date = outboundDate,
),
OriginDestinationLocationInfo(
    origin = locationService.getLocationInfo(iata = destination, type = destinationType),  // 출발지 <-> 도착지 swap
    destination = locationService.getLocationInfo(iata = origin, type = originType),
    date = inboundDate,
)
  • 가는 편: origin -> destination (outboundDate)
  • 오는 편: destination -> origin (inboundDate) - 출발지/도착지 교환

2.4 검색 결과 조회 (getFlights)

소스 위치: FlightSearchInternalController.kt:103-129

기본 정보

항목
HTTP MethodPOST
경로/internals/flights/{listKey}

Path Variables

파라미터타입필수설명
listKeyUUIDY검색 키 (편도/왕복 검색 API 응답의 key 값)

응답 - HTTP Status에 따른 처리

소스 위치: FlightSearchInternalController.kt:107-117

val httpStatus = when (flightSearch.status) {
    PollingStatus.PENDING -> HttpStatus.ACCEPTED      // 202: 검색 진행 중
    PollingStatus.ERROR -> throw when (flightSearch.messageKey) {
        null -> InternationalSearchException(MessageKey.SEARCH_FAILED)
        else -> InternationalSearchException(flightSearch.messageKey).apply {
            messageArguments = flightSearch.message?.let { arrayOf(it) }
        }
    }
    else -> HttpStatus.OK                              // 200: 검색 완료
}
PollingStatusHTTP Status설명
PENDING202 Accepted검색 진행 중, 재요청 필요
ERRORException 발생검색 실패
COMPLETE / NO_DATA200 OK검색 완료

응답 구조 (List)

소스 위치: FlightSearchView.kt:369-401

data class InternalFlightItemView(
    val listKey: UUID,
    val avail: Int,
    val id: String,
    val adultPrice: Long,
    val totalPrice: Long,
    val schedules: List<InternalScheduleSummaryView>,
    val fares: List<InternalFlightFareView>,
)

2.5 캐시 키 상태 확인 (getCacheKeyStatus)

소스 위치: FlightSearchInternalController.kt:131-138

기본 정보

항목
HTTP MethodGET
경로/internals/flights/{listKey}/status
응답 상태200 OK

응답

{
  "status": "OK"
}

캐시 키가 유효하지 않으면 CacheKeyInvalidException 발생


2.6 재발행 검색 (reissueSearch)

소스 위치: FlightSearchInternalController.kt:140-165

기본 정보

항목
HTTP MethodPOST
경로/internals/flights/reissue
응답 상태200 OK

Request Body (ReissueSearchRequest)

소스 위치: ReissueSearchRequest.kt:6-17

data class ReissueSearchRequest(
    val pnr: String,                                    // 필수: PNR 번호
    val supplierIdentificationKey: String?,             // 선택: 공급사 식별 키
    val adult: Int,                                     // 필수: 성인 수
    val child: Int = 0,                                 // 선택: 아동 수 (기본값 0)
    val infant: Int = 0,                                // 선택: 유아 수 (기본값 0)
    val supplier: String,                               // 필수: 공급사 코드
    val originDestinationInfos: List<OriginDestinationInfoRequest>,  // 필수: 여정 정보
    val cabins: Set<CabinType>,                         // 필수: 좌석 등급
    val onlyDirect: Boolean = false,                    // 선택: 직항만 검색 (기본값 false)
    val onlyFreeBaggageInclude: Boolean = false,        // 선택: 무료 수하물 포함만 (기본값 false)
)

OriginDestinationInfoRequest 구조

소스 위치: ReissueSearchRequest.kt:19-23

data class OriginDestinationInfoRequest(
    val origin: String,           // 출발지 IATA 코드
    val destination: String,      // 도착지 IATA 코드
    val departureDate: LocalDate  // 출발일
)

응답

{
  "key": "UUID"
}

3. Request/Response 구조

3.1 공통 열거형 (Enums)

LocationType

소스 위치: LocationType.kt:3-6

enum class LocationType(val shorter: String) {
    CITY(shorter = "c"),
    AIRPORT(shorter = "a");
}

CabinType

소스 위치: CabinType.kt:3-10

enum class CabinType(val value: String) {
    ECONOMY(value = "Y"),           // 이코노미
    PREMIUM_ECONOMY(value = "W"),   // 프리미엄 이코노미
    BUSINESS(value = "C"),          // 비지니스
    FIRST(value = "F")              // 일등석
}

PollingStatus

소스 위치: PollingStatus.kt:3-8

enum class PollingStatus {
    PENDING,    // 검색 진행 중
    ERROR,      // 오류 발생
    COMPLETE,   // 검색 완료
    NO_DATA     // 데이터 없음
}

3.2 Response 모델

InternalFlightItemView

소스 위치: FlightSearchView.kt:369-401

필드타입설명
listKeyUUID검색 리스트 키
availInt예약 가능 좌석 수
idString운임 ID
adultPriceLong성인 1인 가격
totalPriceLong총 가격
schedulesList<InternalScheduleSummaryView>스케줄 요약 정보
faresList<InternalFlightFareView>운임 정보 목록

InternalScheduleSummaryView

소스 위치: FlightSearchView.kt:403-440

필드타입설명
carrierString항공사 코드
carrierTextString항공사 표시명 (공동운항 정보 포함)
carrierLogoUrlString?항공사 로고 URL
departureString출발 공항 코드
arrivalString도착 공항 코드
departureDateTimeLocalDateTime출발 일시
arrivalDateTimeLocalDateTime도착 일시
stopInt경유 횟수
totalFlightTimeString총 비행시간
addDayInt추가 일수 (익일 도착 등)
freeBaggageInternalFreeBaggageView?무료 수하물 정보
cabinsList<CabinType>좌석 등급 목록

InternalFlightFareView

소스 위치: FlightSearchView.kt:456-475

필드타입설명
idString?운임 ID
availInt?예약 가능 좌석 수
detailKeyString?상세 조회 키
adultPriceLong성인 가격
cardPromotionNameString?카드 프로모션명
identityTypeIdentityType신분 유형

4. 비즈니스 로직 흐름

4.1 검색 흐름 개요

sequenceDiagram
    participant Client as 내부 서비스
    participant Controller as FlightSearchInternalController
    participant UseCase as StandardFlightSearchUseCase
    participant Service as FlightSearchService
    participant Cache as FlightSearchRepository

    Client->>Controller: GET /search/{route}/{date}
    Controller->>Controller: SearchInfo 생성
    Controller->>Controller: searchInfo.validate()
    Controller->>UseCase: init(searchInfo, SCHEDULE, false)
    UseCase->>Cache: savePolling(listKey, PENDING)
    UseCase-->>Controller: listKey (UUID)
    Controller-->>Client: {"key": "UUID"}

    Note over UseCase: 비동기 검색 실행 (Coroutine)
    UseCase->>Service: searchFlights()
    Service->>Service: 항공권 검색 및 필터링
    Service->>Cache: savePolling(listKey, COMPLETE)

    Client->>Controller: POST /{listKey}
    Controller->>Service: getFlightSearch(listKey)
    Service->>Cache: find(listKey)
    Cache-->>Service: FlightSearch
    Controller-->>Client: List<InternalFlightItemView>

4.2 검색 초기화 (searchFlightsListKey)

소스 위치: FlightSearchInternalController.kt:167-195

private fun searchFlightsListKey(
    adult: Int,
    child: Int,
    infant: Int,
    freeBaggageOnly: Boolean,
    cabins: Set<CabinType>,
    useCache: Boolean,
    vararg originDestinationLocationInfo: OriginDestinationLocationInfo,
): Map<String, UUID> {
    val searchInfo = SearchInfo(
        adult = adult,
        child = child,
        infant = infant,
        freeBaggageOnly = freeBaggageOnly,
        cabins = cabins,
        originDestinationLocationInfos = originDestinationLocationInfo.toList(),
        useCache = useCache,
    )
 
    searchInfo.validate(bookableDateRange = bookableDateService.getBookableDateRange())
 
    return mapOf(
        "key" to standardFlightSearchUseCase.init(
            searchInfo = searchInfo,
            flightGroupCriteria = FlightGroupCriteria.SCHEDULE,  // Internal은 SCHEDULE 고정
            useRecommendation = false                             // Internal은 추천 비활성화
        )
    )
}

4.3 StandardFlightSearchUseCase.init()

소스 위치: StandardFlightSearchUseCase.kt:84-101

fun init(searchInfo: SearchInfo, flightGroupCriteria: FlightGroupCriteria, useRecommendation: Boolean): UUID {
    return CacheKeyGenerator.getFlightSearchCacheKey().also { listKey ->
        // 1. PENDING 상태로 캐시 저장
        flightSearchService.savePolling(
            listKey = listKey,
            result = FlightSearch.pending(tripType = searchInfo.getTripType()),
        )
        // 2. 비동기로 검색 실행
        CoroutineScope(Dispatchers.IO).withLaunch {
            searchFlights(
                searchInfo = searchInfo,
                onlyDirect = false,
                cachedListKey = listKey,
                flightGroupCriteria = flightGroupCriteria,
                useRecommendation = useRecommendation,
                usePolling = true,
            )
        }
    }
}

4.4 그룹핑 처리

소스 위치: StandardFlightSearchUseCase.kt:51-58

when (flightGroupCriteria) {
    FlightGroupCriteria.NONE -> flightItems
    else -> flightItems
        .groupBy { flightItem -> flightGroupCriteria.getKey(flightItem) }
        .map { (groupKey, flightItems) ->
            flightItems.pickOptimalFlight(fareDecisionStrategy).copy(groupKey = groupKey)
        }
}

Internal API는 FlightGroupCriteria.SCHEDULE 사용:

  • 동일 스케줄 키로 그룹핑 (marketingCarrier + flightNumber + departureAt.dayOfWeek)
  • ITX 운임과 일반 운임 경합 가능

5. FlightSearchController와의 차이점 분석

5.1 비교 표

항목FlightSearchController (외부용)FlightSearchInternalController (내부용)
Base Path/flights/internals/flights
소스 위치controller/FlightSearchController.ktcontroller/internal/FlightSearchInternalController.kt
지원 여정편도, 왕복, 다구간 (2~4구간)편도, 왕복만
FlightGroupCriteriaNONESCHEDULE
useRecommendationtruefalse
응답 타입FlightSearchPageView (페이지네이션 지원)List<InternalFlightItemView> (단순 리스트)
재발행 검색미지원지원 (/reissue)
필터/정렬 데이터포함 (FilterDataView, SortDataView)미포함
Request BodyFlightSearchRequest (필터, 정렬, 페이지)없음
AirportService 의존성OX

5.2 상세 차이점

5.2.1 그룹핑 기준 차이

외부용 (FlightSearchController)

// FlightSearchController.kt:298-301
standardFlightSearchUseCase.init(
    searchInfo = searchInfo,
    flightGroupCriteria = FlightGroupCriteria.NONE,  // 그룹핑 없음
    useRecommendation = true
)

내부용 (FlightSearchInternalController)

// FlightSearchInternalController.kt:189-193
standardFlightSearchUseCase.init(
    searchInfo = searchInfo,
    flightGroupCriteria = FlightGroupCriteria.SCHEDULE,  // 스케줄 기준 그룹핑
    useRecommendation = false
)

5.2.2 응답 처리 차이

외부용: 페이지네이션, 필터링, 정렬 지원

// FlightSearchController.kt:320-327
HttpStatus.OK to FlightSearchPageView.ofComplete(
    flightSearch = flightSearch,
    flightItems = flightSearch.flightItems,
    request = request,
    airlineMap = airlineService.getAirlinesMap(flightSearch.airlineSet),
    airportMap = airportService.getAirportsMap(flightSearch.airportSet),
)

내부용: 단순 리스트 반환

// FlightSearchInternalController.kt:120-128
ResponseEntity(
    flightSearch.flightItems.map {
        InternalFlightItemView.of(
            flightItem = it,
            airlineMap = airlineMap,
        )
    },
    httpStatus
)

6. 재발행(Reissue) 검색 로직 상세

6.1 재발행 검색 흐름

sequenceDiagram
    participant Client as 내부 서비스
    participant Controller as FlightSearchInternalController
    participant UseCase as ReissueFlightSearchUseCase
    participant Adapter as AdapterClient
    participant Cache as FlightSearchRepository

    Client->>Controller: POST /reissue (ReissueSearchRequest)
    Controller->>Controller: SearchInfo.of(request)
    Controller->>UseCase: init(searchInfo, supplier, pnr, ...)
    UseCase->>Cache: savePolling(listKey, PENDING)
    UseCase-->>Controller: listKey (UUID)
    Controller-->>Client: {"key": "UUID"}

    Note over UseCase: 비동기 검색 실행 (Coroutine)
    UseCase->>Adapter: getFareItinerariesByPnr()
    Adapter-->>UseCase: List<FareItineraryResponse>
    UseCase->>UseCase: FlightItem.ofReissue() 변환
    UseCase->>Cache: savePolling(listKey, COMPLETE)

6.2 ReissueFlightSearchUseCase.init()

소스 위치: ReissueFlightSearchUseCase.kt:93-116

fun init(
    searchInfo: SearchInfo,
    supplier: String,
    pnr: String,
    supplierIdentificationKey: String?,
    directOnly: Boolean,
): UUID {
    return CacheKeyGenerator.getFlightSearchCacheKey().also { listKey ->
        // 1. PENDING 상태로 캐시 저장
        flightSearchService.savePolling(
            listKey = listKey,
            result = FlightSearch.pending(tripType = searchInfo.getTripType()),
        )
        // 2. 비동기로 재발행 검색 실행
        CoroutineScope(Dispatchers.IO).withLaunch {
            searchFlights(
                supplier = supplier,
                pnr = pnr,
                supplierIdentificationKey = supplierIdentificationKey,
                searchInfo = searchInfo,
                directOnly = directOnly,
                listKey = listKey,
            )
        }
    }
}

6.3 재발행 검색 실행

소스 위치: ReissueFlightSearchUseCase.kt:27-91

fun searchFlights(
    supplier: String,
    pnr: String,
    supplierIdentificationKey: String?,
    searchInfo: SearchInfo,
    listKey: UUID,
    directOnly: Boolean,
): List<FlightItem> {
    try {
        val funnel: Funnel = MDCHolder.SalesFunnel.get()
 
        // AdapterClient를 통해 PNR 기반 운임 조회
        val fareItineraries = adapterClient.getFareItinerariesByPnr(
            supplier = supplier,
            pnr = pnr,
            supplierIdentificationKey = supplierIdentificationKey,
            adult = searchInfo.adult,
            child = searchInfo.child,
            infant = searchInfo.infant,
            originDestinationLocationInfos = searchInfo.originDestinationLocationInfos,
            cabins = searchInfo.cabins,
            directOnly = directOnly,
            freeBaggageOnlyInclude = searchInfo.freeBaggageOnly
        )
 
        // FlightItem으로 변환 (재발행용)
        return fareItineraries.map {
            FlightItem.ofReissue(
                listKey = listKey,
                funnel = funnel,
                fareItinerary = it,
                passengerCountMap = mapOf(
                    PassengerType.ADULT to searchInfo.adult,
                    PassengerType.CHILD to searchInfo.child,
                    PassengerType.INFANT to searchInfo.infant
                ),
            )
        }.also {
            // 성공 시 COMPLETE 상태로 저장
            flightSearchService.savePolling(
                listKey = listKey,
                result = FlightSearch.complete(items = it, tripType = searchInfo.getTripType()),
            )
        }
    } catch (e: Exception) {
        // 에러 처리 (아래 섹션 참조)
    }
}

6.4 재발행용 FlightItem 생성

소스 위치: FlightItem.kt:297-327

일반 검색과 달리 재발행 검색은:

  • 프로모션 정책 적용 없음
  • 운임 할인 적용 없음
  • 단일 FlightFare만 생성
fun ofReissue(
    listKey: UUID,
    funnel: Funnel,
    fareItinerary: FareItineraryResponse,
    passengerCountMap: Map<PassengerType, Int>,
): FlightItem {
    val itinerary = getItinerarySummary(fareItinerary.schedules)
    val passengerFares = fareItinerary.passengerFares
    val adultIdentityType = passengerFares.first { it.type == PassengerType.ADULT }.type
    return FlightItem(
        listKey = listKey,
        supplier = fareItinerary.supplier,
        scheduleKey = fareItinerary.scheduleKey,
        score = null,
        tags = null,
        schedules = fareItinerary.schedules.map { Schedule.of(it) },
        validatingCarrier = fareItinerary.validatingCarrier,
        fares = listOf(
            FlightFare.of(
                listKey = listKey,
                fareItinerary = fareItinerary,
                funnel = funnel,
                passengerCountMap = passengerCountMap,
                adultIdentityType = adultIdentityType.name,
                itinerarySummary = itinerary
            )
        ),
        tripDirectionType = null,
        prepayment = null,
    )
}

7. 에러 처리 및 예외 상황

7.1 예외 클래스 구조

소스 위치: Exceptions.kt

classDiagram
    RuntimeException <|-- ApiException
    ApiException <|-- InternationalSearchException
    ApiException <|-- CacheKeyInvalidException
    ApiException <|-- MethodArgumentInvalidException

    class ApiException {
        +messageKey: MessageKey
        +objs: Array
        +messageArguments: Array?
        +capturable: Boolean
        +notifiable: Boolean
    }

7.2 주요 예외 케이스

예외 클래스MessageKey발생 상황
InternationalSearchExceptionSEARCH_FAILED검색 실패 (PollingStatus.ERROR)
InternationalSearchExceptionREISSUE_SEARCH_FAILED재발행 검색 실패
CacheKeyInvalidExceptionINVALID_CACHE_KEY유효하지 않은 listKey
MethodArgumentInvalidExceptionINVALID_PARAMETER잘못된 파라미터 (IATA 코드 등)
MethodArgumentInvalidExceptionINVALID_DATES잘못된 날짜 (오는 날 < 가는 날)
MethodArgumentInvalidExceptionINVALID_ITINERARY잘못된 여정 (출발지=도착지, 국내선)
MethodArgumentInvalidExceptionINVALID_BOOKABLE_DATE예약 불가 날짜

7.3 검색 결과 조회 에러 처리

소스 위치: FlightSearchInternalController.kt:107-117

PollingStatus.ERROR -> throw when (flightSearch.messageKey) {
    null -> InternationalSearchException(MessageKey.SEARCH_FAILED)
    else -> InternationalSearchException(flightSearch.messageKey).apply {
        messageArguments = flightSearch.message?.let { arrayOf(it) }
    }
}

7.4 재발행 검색 에러 처리

소스 위치: ReissueFlightSearchUseCase.kt:67-90

throw when (e) {
    is ApiException -> {
        // ApiException인 경우: 에러 상태로 캐시 저장 후 재발행 실패 예외 발생
        flightSearchService.savePolling(
            listKey = listKey,
            result = FlightSearch.error(
                e.messageKey,
                e.messageArguments?.joinToString("\n"),
            ),
        )
        InternationalSearchException(MessageKey.REISSUE_SEARCH_FAILED).apply {
            this.messageArguments = e.messageArguments
        }
    }
    else -> {
        // 기타 예외: 에러 상태로 캐시 저장 후 재발행 실패 예외 발생
        flightSearchService.savePolling(
            listKey = listKey,
            result = FlightSearch.error(e.message),
        )
        InternationalSearchException(MessageKey.REISSUE_SEARCH_FAILED)
    }
}

7.5 유효성 검증 에러

소스 위치: SearchInfo.kt:46-98

검증 항목MessageKey조건
예약 가능 날짜INVALID_BOOKABLE_DATE날짜가 예약 가능 범위 외
날짜 순서INVALID_DATES이전 여정 날짜 > 이후 여정 날짜
여정 유효성INVALID_ITINERARY출발지 = 도착지, 국내선 검색
제한 국가INVALID_ITINERARY_RESTRICTED_COUNTRYSY, IR, UA, CU 포함 여정
멀티티켓 검증INVALID_PARAMETERINBOUND 검색 시 detailKey 누락

8. 관련 도메인 모델 분석

8.1 FlightSearch (검색 결과 캐시 모델)

소스 위치: FlightSearch.kt:10-80

data class FlightSearch(
    val status: PollingStatus,              // 검색 상태
    val messageKey: MessageKey? = null,     // 에러 메시지 키
    val message: String? = null,            // 에러 메시지
    val flightItems: List<FlightItem> = emptyList(),  // 검색된 항공편 목록
    val defaultSortType: SortType = SortType.PRICE,   // 기본 정렬 유형
    val tripType: TripType? = null,         // 여행 유형
    val airlineSet: Set<String> = emptySet(),   // 항공사 코드 집합
    val airportSet: Set<String> = emptySet(),   // 공항 코드 집합
    val searchInfoKey: String? = null,      // 검색 정보 키
) : Serializable

상태 생성 메서드

메서드상태용도
pending(tripType)PENDING검색 시작 시
complete(items, tripType)COMPLETE검색 완료 시
error(message)ERROR검색 실패 시

8.2 FlightItem (항공편 아이템)

소스 위치: FlightItem.kt:21-329

data class FlightItem(
    val listKey: UUID,                      // 검색 리스트 키
    val supplier: String,                   // 공급사 코드
    val scheduleKey: String,                // 스케줄 키
    val validatingCarrier: String,          // 발권 항공사
    val schedules: List<Schedule>,          // 스케줄 목록 (여정)
    val fares: List<FlightFare>,            // 운임 목록
    var score: BigDecimal?,                 // 추천 점수
    var tags: List<RecommendationTag>?,     // 추천 태그
    val tripDirectionType: TripDirectionType?,  // 여행 방향 (편도, 왕복 등)
    val prepayment: Boolean?,               // 선결제 여부
    val groupKey: String = "",              // 그룹 키
)

파생 속성

속성타입설명
isMixBoolean믹스 항공권 여부 (tripDirectionType != null)
lowestFareFlightFare최저가 운임
isDirectBoolean직항 여부
maxStopInt최대 경유 횟수
outboundDepartureAtLocalDateTime출발 일시
inboundArrivalAtLocalDateTime최종 도착 일시

8.3 SearchInfo (검색 정보)

소스 위치: SearchInfo.kt:15-106

data class SearchInfo(
    val adult: Int,                         // 성인 수
    val child: Int,                         // 아동 수
    val infant: Int,                        // 유아 수
    val freeBaggageOnly: Boolean,           // 무료 수하물 포함만
    val cabins: Set<CabinType>,             // 좌석 등급
    val originDestinationLocationInfos: List<OriginDestinationLocationInfo>,  // 여정 정보
    val multiTicket: MultiTicket? = null,   // 멀티티켓 정보
    val useCache: Boolean,                  // 캐시 사용 여부
)

8.4 OriginDestinationLocationInfo (여정 정보)

소스 위치: OriginDestinationLocationInfo.kt:11-30

data class OriginDestinationLocationInfo(
    val origin: LocationInfo,       // 출발지 정보
    val destination: LocationInfo,  // 도착지 정보
    val date: LocalDate,            // 출발일
)

8.5 LocationInfo (위치 정보)

소스 위치: OriginDestinationLocationInfo.kt:32-93

sealed class LocationInfo(val type: LocationType) {
    abstract val iata: String           // IATA 코드
    abstract val airportIatas: Set<String>  // 공항 코드 집합
    abstract val cityIata: String       // 도시 코드
    abstract val countryCode: String    // 국가 코드
    abstract val zoneId: ZoneId         // 시간대
 
    // 하위 클래스
    data class City(...) : LocationInfo(LocationType.CITY)
    data class Airport(...) : LocationInfo(LocationType.AIRPORT)
}

8.6 FlightGroupCriteria (그룹핑 기준)

소스 위치: FlightGroupCriteria.kt:1-59

enum class FlightGroupCriteria {
    NONE,                       // 그룹핑 없음 (외부 API용)
    SCHEDULE,                   // 스케줄 기준 (내부 API용)
    SCHEDULE_WITH_CLASS,        // 스케줄 + 예약 클래스 기준
    SCHEDULE_WITH_FREE_BAGGAGE, // 스케줄 + 무료 수하물 기준 (메타 검색용)
}

9. 의존성 구조

9.1 Controller 의존성

소스 위치: FlightSearchInternalController.kt:25-33

class FlightSearchInternalController(
    private val flightSearchService: FlightSearchService,
    private val standardFlightSearchUseCase: StandardFlightSearchUseCase,
    private val reissueFlightSearchUseCase: ReissueFlightSearchUseCase,
    private val airlineService: AirlineService,
    private val bookableDateService: BookableDateService,
    private val locationService: LocationService,
    private val billingService: BillingService,
)

9.2 의존성 다이어그램

graph TD
    Controller[FlightSearchInternalController]

    Controller --> StandardUseCase[StandardFlightSearchUseCase]
    Controller --> ReissueUseCase[ReissueFlightSearchUseCase]
    Controller --> SearchService[FlightSearchService]
    Controller --> AirlineService[AirlineService]
    Controller --> BookableService[BookableDateService]
    Controller --> LocationService[LocationService]
    Controller --> BillingService[BillingService]

    StandardUseCase --> SearchService
    StandardUseCase --> BookableService

    ReissueUseCase --> SearchService
    ReissueUseCase --> AdapterClient[AdapterClient]

    SearchService --> AdapterClient
    SearchService --> Repository[FlightSearchRepository]
    SearchService --> ItemRepo[FlightItemRepository]

    LocationService --> CityService[CityService]
    LocationService --> AirportService[AirportService]

10. 참고 사항

10.1 폴링(Polling) 패턴

이 API는 폴링 패턴을 사용합니다:

  1. 클라이언트가 검색 요청 (GET /search/...)
  2. 서버가 즉시 listKey 반환
  3. 백그라운드에서 비동기 검색 실행
  4. 클라이언트가 listKey로 결과 폴링 (POST /{listKey})
  5. PENDING 상태면 재요청, COMPLETE/ERROR 상태면 완료

10.2 캐시 키 생성

소스 위치: CacheKeyGenerator.kt (별도 파일)

CacheKeyGenerator.getFlightSearchCacheKey()로 UUID 기반 캐시 키 생성

10.3 비동기 처리

CoroutineScope(Dispatchers.IO).withLaunch {
    // 검색 로직
}

Kotlin Coroutine을 사용하여 IO 디스패처에서 비동기 검색 실행


부록: 소스 파일 위치 요약

클래스/파일경로
FlightSearchInternalControllerinterfaces/controller/internal/FlightSearchInternalController.kt
FlightSearchControllerinterfaces/controller/FlightSearchController.kt
StandardFlightSearchUseCaseapplication/usecase/StandardFlightSearchUseCase.kt
ReissueFlightSearchUseCaseapplication/usecase/ReissueFlightSearchUseCase.kt
FlightSearchServiceapplication/FlightSearchService.kt
LocationServiceapplication/LocationService.kt
ReissueSearchRequestinterfaces/request/ReissueSearchRequest.kt
InternalFlightItemViewinterfaces/response/FlightSearchView.kt:369-401
FlightSearchdomain/FlightSearch.kt
FlightItemdomain/FlightItem.kt
SearchInfosupport/model/SearchInfo.kt
OriginDestinationLocationInfosupport/model/OriginDestinationLocationInfo.kt
LocationTypesupport/enums/LocationType.kt
CabinTypesupport/enums/CabinType.kt
PollingStatussupport/enums/PollingStatus.kt
FlightGroupCriteriasupport/enums/FlightGroupCriteria.kt
MessageKeysupport/MessageKey.kt
Exceptionssupport/exception/Exceptions.kt

관련 문서