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}/status | getCacheKeyStatus | 캐시 키 상태 확인 |
| POST | /internals/flights/reissue | reissueSearch | 재발행 검색 시작 |
2.2 편도 검색 (searchOneWayTripFlightsListKey)
소스 위치: FlightSearchInternalController.kt:34-63
기본 정보
| 항목 | 값 |
|---|---|
| HTTP Method | GET |
| 경로 | /internals/flights/search/{originType}:{origin}-{destinationType}:{destination}/{outboundDate} |
| 응답 상태 | 200 OK |
Path Variables
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
originType | LocationType | Y | 출발지 타입 (CITY 또는 AIRPORT) |
origin | String | Y | 출발지 IATA 코드 |
destinationType | LocationType | Y | 도착지 타입 (CITY 또는 AIRPORT) |
destination | String | Y | 도착지 IATA 코드 |
outboundDate | LocalDate | Y | 출발일 (ISO 형식: yyyy-MM-dd) |
Query Parameters
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
cabins | Set<CabinType> | Y | - | 좌석 등급 (ECONOMY, PREMIUM_ECONOMY, BUSINESS, FIRST) |
adult | Int | Y | - | 성인 수 |
child | Int | N | 0 | 아동 수 |
infant | Int | N | 0 | 유아 수 |
freeBaggageOnly | Boolean | N | false | 무료 수하물 포함 항공편만 |
useCache | Boolean | N | true | 상세 조회용 캐싱 사용 여부 |
응답
{
"key": "UUID"
}2.3 왕복 검색 (searchRoundTripFlightsListKey)
소스 위치: FlightSearchInternalController.kt:65-101
기본 정보
| 항목 | 값 |
|---|---|
| HTTP Method | GET |
| 경로 | /internals/flights/search/{originType}:{origin}-{destinationType}:{destination}/{outboundDate}/{inboundDate} |
| 응답 상태 | 200 OK |
Path Variables
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
originType | LocationType | Y | 출발지 타입 |
origin | String | Y | 출발지 IATA 코드 |
destinationType | LocationType | Y | 도착지 타입 |
destination | String | Y | 도착지 IATA 코드 |
outboundDate | LocalDate | Y | 가는 날 (ISO 형식) |
inboundDate | LocalDate | Y | 오는 날 (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 Method | POST |
| 경로 | /internals/flights/{listKey} |
Path Variables
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
listKey | UUID | Y | 검색 키 (편도/왕복 검색 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: 검색 완료
}| PollingStatus | HTTP Status | 설명 |
|---|---|---|
PENDING | 202 Accepted | 검색 진행 중, 재요청 필요 |
ERROR | Exception 발생 | 검색 실패 |
COMPLETE / NO_DATA | 200 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 Method | GET |
| 경로 | /internals/flights/{listKey}/status |
| 응답 상태 | 200 OK |
응답
{
"status": "OK"
}캐시 키가 유효하지 않으면 CacheKeyInvalidException 발생
2.6 재발행 검색 (reissueSearch)
소스 위치: FlightSearchInternalController.kt:140-165
기본 정보
| 항목 | 값 |
|---|---|
| HTTP Method | POST |
| 경로 | /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
| 필드 | 타입 | 설명 |
|---|---|---|
listKey | UUID | 검색 리스트 키 |
avail | Int | 예약 가능 좌석 수 |
id | String | 운임 ID |
adultPrice | Long | 성인 1인 가격 |
totalPrice | Long | 총 가격 |
schedules | List<InternalScheduleSummaryView> | 스케줄 요약 정보 |
fares | List<InternalFlightFareView> | 운임 정보 목록 |
InternalScheduleSummaryView
소스 위치: FlightSearchView.kt:403-440
| 필드 | 타입 | 설명 |
|---|---|---|
carrier | String | 항공사 코드 |
carrierText | String | 항공사 표시명 (공동운항 정보 포함) |
carrierLogoUrl | String? | 항공사 로고 URL |
departure | String | 출발 공항 코드 |
arrival | String | 도착 공항 코드 |
departureDateTime | LocalDateTime | 출발 일시 |
arrivalDateTime | LocalDateTime | 도착 일시 |
stop | Int | 경유 횟수 |
totalFlightTime | String | 총 비행시간 |
addDay | Int | 추가 일수 (익일 도착 등) |
freeBaggage | InternalFreeBaggageView? | 무료 수하물 정보 |
cabins | List<CabinType> | 좌석 등급 목록 |
InternalFlightFareView
소스 위치: FlightSearchView.kt:456-475
| 필드 | 타입 | 설명 |
|---|---|---|
id | String? | 운임 ID |
avail | Int? | 예약 가능 좌석 수 |
detailKey | String? | 상세 조회 키 |
adultPrice | Long | 성인 가격 |
cardPromotionName | String? | 카드 프로모션명 |
identityType | IdentityType | 신분 유형 |
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.kt | controller/internal/FlightSearchInternalController.kt |
| 지원 여정 | 편도, 왕복, 다구간 (2~4구간) | 편도, 왕복만 |
| FlightGroupCriteria | NONE | SCHEDULE |
| useRecommendation | true | false |
| 응답 타입 | FlightSearchPageView (페이지네이션 지원) | List<InternalFlightItemView> (단순 리스트) |
| 재발행 검색 | 미지원 | 지원 (/reissue) |
| 필터/정렬 데이터 | 포함 (FilterDataView, SortDataView) | 미포함 |
| Request Body | FlightSearchRequest (필터, 정렬, 페이지) | 없음 |
| AirportService 의존성 | O | X |
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 | 발생 상황 |
|---|---|---|
InternationalSearchException | SEARCH_FAILED | 검색 실패 (PollingStatus.ERROR) |
InternationalSearchException | REISSUE_SEARCH_FAILED | 재발행 검색 실패 |
CacheKeyInvalidException | INVALID_CACHE_KEY | 유효하지 않은 listKey |
MethodArgumentInvalidException | INVALID_PARAMETER | 잘못된 파라미터 (IATA 코드 등) |
MethodArgumentInvalidException | INVALID_DATES | 잘못된 날짜 (오는 날 < 가는 날) |
MethodArgumentInvalidException | INVALID_ITINERARY | 잘못된 여정 (출발지=도착지, 국내선) |
MethodArgumentInvalidException | INVALID_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_COUNTRY | SY, IR, UA, CU 포함 여정 |
| 멀티티켓 검증 | INVALID_PARAMETER | INBOUND 검색 시 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 = "", // 그룹 키
)파생 속성
| 속성 | 타입 | 설명 |
|---|---|---|
isMix | Boolean | 믹스 항공권 여부 (tripDirectionType != null) |
lowestFare | FlightFare | 최저가 운임 |
isDirect | Boolean | 직항 여부 |
maxStop | Int | 최대 경유 횟수 |
outboundDepartureAt | LocalDateTime | 출발 일시 |
inboundArrivalAt | LocalDateTime | 최종 도착 일시 |
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는 폴링 패턴을 사용합니다:
- 클라이언트가 검색 요청 (GET
/search/...) - 서버가 즉시
listKey반환 - 백그라운드에서 비동기 검색 실행
- 클라이언트가
listKey로 결과 폴링 (POST/{listKey}) PENDING상태면 재요청,COMPLETE/ERROR상태면 완료
10.2 캐시 키 생성
소스 위치: CacheKeyGenerator.kt (별도 파일)
CacheKeyGenerator.getFlightSearchCacheKey()로 UUID 기반 캐시 키 생성
10.3 비동기 처리
CoroutineScope(Dispatchers.IO).withLaunch {
// 검색 로직
}Kotlin Coroutine을 사용하여 IO 디스패처에서 비동기 검색 실행
부록: 소스 파일 위치 요약
| 클래스/파일 | 경로 |
|---|---|
| FlightSearchInternalController | interfaces/controller/internal/FlightSearchInternalController.kt |
| FlightSearchController | interfaces/controller/FlightSearchController.kt |
| StandardFlightSearchUseCase | application/usecase/StandardFlightSearchUseCase.kt |
| ReissueFlightSearchUseCase | application/usecase/ReissueFlightSearchUseCase.kt |
| FlightSearchService | application/FlightSearchService.kt |
| LocationService | application/LocationService.kt |
| ReissueSearchRequest | interfaces/request/ReissueSearchRequest.kt |
| InternalFlightItemView | interfaces/response/FlightSearchView.kt:369-401 |
| FlightSearch | domain/FlightSearch.kt |
| FlightItem | domain/FlightItem.kt |
| SearchInfo | support/model/SearchInfo.kt |
| OriginDestinationLocationInfo | support/model/OriginDestinationLocationInfo.kt |
| LocationType | support/enums/LocationType.kt |
| CabinType | support/enums/CabinType.kt |
| PollingStatus | support/enums/PollingStatus.kt |
| FlightGroupCriteria | support/enums/FlightGroupCriteria.kt |
| MessageKey | support/MessageKey.kt |
| Exceptions | support/exception/Exceptions.kt |