Jeju Air — 개요
module-jejuair arch-supplier-module api-rest pattern-overview
이 노트의 위치
이 문서는 air-intl-adapter의 11개 공급사 모듈 중 Jeju Air(제주항공, 캐리어코드
7C) 모듈의 진입점입니다. 전체 아키텍처는 system-architecture, 요청 한 건이 흐르는 경로는 request-flow를 먼저 읽고 오면 이해가 빠릅니다. 오퍼레이션 디테일은 jejuair-operations, 프로토콜/암호화는 jejuair-protocol, 함정은 jejuair-pitfalls로 이어집니다.
1. 공급사 특징 — 왜 이렇게 연동하는가
1.1 LCC + 자체 REST API (GDS/NDC 아님)
제주항공은 **LCC(저비용 항공사, Low-Cost Carrier)**이며, 이 모듈은 항공사 자체 REST(JSON) API에 직결합니다. GDS(Amadeus·Sabre·Galileo의 SOAP)나 NDC 표준(Korean Air·Lufthansa·Singapore)과 달리, 제주항공 PSS(여객 서비스 시스템)가 노출하는 사설 JSON 엔드포인트를 직접 호출합니다.
코드로 확인되는 근거:
| 근거 | 위치 | 의미 |
|---|---|---|
Supplier.JEJUAIR 고정 | domain/model/FareItinerary.kt:30 | 단일 공급사 모듈 |
REST 엔드포인트 .../booking/getAvailability/v1.0 등 | infrastructure/JejuairClient.kt | HTTP POST + JSON, SOAP 아님 |
MediaType.APPLICATION_JSON_VALUE 헤더 | JejuairClient.kt:53-54 | Content-Type/Accept 모두 JSON |
OkHttp 기반 ClientSupport 상속 | JejuairClient.kt:42 | GDS SOAP 스택과 무관한 순수 HTTP 클라이언트 |
JejuairResponse<T> 제네릭 래퍼 | infrastructure/response/JejuairResponse.kt | JSON 응답 공통 봉투 + pssToken 헤더 |
"LCC라서 단순하다"는 착각 금지
LCC REST지만 이 모듈은 **검색·예약·발권·환불·재발행·PNR 분리(divide)**까지 풀 라이프사이클을 다룹니다. 단순 검색 어댑터가 아니라, 결제(
requestApprovalPay)와 재발행 차액 정산까지 직접 수행하는 무거운 모듈입니다.
1.2 비즈니스/시장 맥락 — 국내 1위 LCC, 한국 출발 핵심 노선
- 제주항공은 한국 최대 LCC로, 한국발 단거리 국제선(일본·동남아·중화권)의 핵심 재고원입니다.
- LCC 특성상 GDS를 경유하지 않고 항공사 직판 채널(PSS)에 직접 붙는 것이 보편적입니다. 그래서 GDS 세션(PNR 세션·SOAP 인증) 대신 요청마다 발급되는
PssToken으로 상태를 이어붙입니다. - 결제 정보 보호: 카드 정보는 항공사 공개키로 RSA 암호화 후 전송합니다(jejuair-protocol 참조).
1.3 “취소 코루틴 흔적” — 실제 코드 검증
상위 컨텍스트에서 언급된 “취소 코루틴”은 발권 실패 시 비동기 자동 취소(보상 트랜잭션) 패턴으로 실재합니다.
// application/JejuairTicketingService.kt:114-128
private fun cancelAsync(pnr: String) {
CoroutineScope(Dispatchers.IO).withLaunch {
delay(5000) // 5초 지연 후 취소 시도
try {
jejuairCancelService.cancel(pnr)
} catch (e: Exception) {
slackService.sendCancelFail(supplier = Supplier.JEJUAIR, pnr = pnr, reason = e.message)
throw e
}
}
}issue()에서 발권(paymentAndIssue)이 MethodArgumentInvalidException(= 결제 거절 등 사용자 귀책)이 아닌 예외로 실패하면, 응답은 즉시 예외를 던지되 백그라운드 코루틴으로 5초 뒤 자동 취소를 시도해 PNR이 미완성 상태로 방치되는 것을 막습니다(JejuairTicketingService.kt:42-47).
코루틴은 어디에 더 있나
- 검색 병렬화:
JejuairFlightSearchService.search()의withBlocking(Dispatchers.IO) { ... .pmap { ... } }(JejuairFlightSearchService.kt:51-54) — 출발-도착 쌍을 카테시안 곱으로 펼쳐 병렬 검색.- fire-and-forget 캐시 정리:
BookingService/FareRuleService의CoroutineScope(Dispatchers.IO).withLaunch { ... }— SOLD_OUT 시 검색키 제거 + 미노출 운임 저장.비동기 공통 유틸은 async-coroutines 참고.
withLaunch/withBlocking/pmap는support/util/CoroutineExtensions.kt소속.
2. 모듈 규모와 서브패키지 구조
99개 .kt 파일 / 약 5,131 라인으로, groupair(34/1,921) 다음으로 작은 축에 속합니다(amadeus 873파일, sabre 882파일과 대비). LCC라 스키마가 단순하고 오퍼레이션이 4종(Search/Booking/Ticketing/FareRule)으로 좁기 때문입니다. 단, 파일 수의 절대다수(약 70개)는 infrastructure/request·infrastructure/response의 JSON DTO입니다.
supplier/jejuair/
├── application/ # 7개 서비스 (오케스트레이션 계층)
│ ├── JejuairFlightSearchService.kt # 검색 + 재발행 검색/상세
│ ├── JejuairPricingService.kt # getTripSell 운임 확정
│ ├── JejuairBookingService.kt # 예약 생성/조회/확정/분리
│ ├── JejuairCancelService.kt # 취소 가능 판정 + 취소(@Retryable)
│ ├── JejuairTicketingService.kt # 발권 + 재발행(reissue) + 취소 코루틴
│ └── JejuairFareRuleService.kt # 운임규정 조회(캐시)
├── infrastructure/ # 외부 PSS REST 호출
│ ├── JejuairClient.kt # 단일 클라이언트, 12개 엔드포인트
│ ├── request/ (~30 DTO) # AvailabilityRQ, CreateBookRQ, PaymentRQ ...
│ └── response/ (~40 DTO) # AvailabilityRS, RetrieveRS, CancelFeeRS ...
├── domain/
│ ├── model/FareItinerary.kt # 검색 결과 도메인(캐시 직렬화 대상)
│ └── repository/JejuairFareItineraryRepository.kt # Redis 저장소
├── interfaces/
│ └── controller/internals/ # 4개 REST 컨트롤러 (Triple 내부 API)
├── support/
│ ├── enums/ (6개) # LiftStatus, SegmentStatus, ProductClass ...
│ ├── model/ (8개) # Booking, Passenger, Refund, Ticket ...
│ └── util/ (6개) # JejuairCipher(RSA), FormatUtils, BaggagePolicy ...
└── configuration/
└── RedisConfiguration.kt # FareItinerary 전용 Gzip Redis 템플릿
JejuairProperties는 모듈 밖채널/퍼널별 엔드포인트·인증키를 담는
JejuairProperties는 공통 설정에 있습니다(configuration/Properties.kt:616). 모듈 내configuration/에는 Redis 템플릿(JejuairRedisConfiguration)만 존재합니다. 설정 전반은 configuration-and-infra 참고.
3. 핵심 파일 표
| 파일 | 역할 | 핵심 포인트 |
|---|---|---|
infrastructure/JejuairClient.kt (681줄) | 유일한 외부 통신 진입점 | 12개 PSS 엔드포인트, PssToken 헤더 주입, @Retryable, 공통 디시리얼라이저 |
application/JejuairFlightSearchService.kt | 검색 오케스트레이션 | pmap 병렬 검색, 캐시키↔운임 매핑, 재발행 차액 검증 |
application/JejuairBookingService.kt | 예약 생성/조회/확정/분리 | 가격확정→예약 2단계, SOLD_OUT 시 비동기 캐시 정리, divide 검증 |
application/JejuairTicketingService.kt | 발권 + 재발행 | 결제→재조회, 실패 시 5초 지연 비동기 취소 |
application/JejuairCancelService.kt | 취소 가능 판정 + 취소 | @Retryable(토큰 재발급), VOID vs REFUND 분기 |
support/util/JejuairCipher.kt | 카드정보 RSA 암호화 | Cipher.getInstance("RSA"), 공개키 하드코딩, decrypt 미지원 |
infrastructure/request/CreditCardInfo.kt | 결제 카드 DTO | 8개 필드에 @Encrypt(cipher = JejuairCipher::class) |
domain/model/FareItinerary.kt | 검색결과 도메인 + 캐시 키 | id=SHA3 해시, itemKey, validate()(child/infant 운임 존재 검증) |
support/model/Booking.kt | 예약 도메인 | isVoidable(당일발권), nonTicketing, groupedSchedules, 재발행 차액 계산 |
configuration/RedisConfiguration.kt | Redis 직렬화 | GzipRedisSerializer + Jackson, FareItinerary 전용 템플릿 |
4. 공개 인터페이스 (컨트롤러 → application)
중앙 디스패처 없음
이 시스템은 중앙 라우터가 없습니다. 공급사 컨트롤러 자체가 Triple 예약 시스템의 내부 API로 노출됩니다(경로 prefix
/internals/JEJUAIR/...). 호출 관계 전체 지도는 caller-callee-map 참고.
4.1 컨트롤러 4종과 엔드포인트
| 컨트롤러 | @RequestMapping | 엔드포인트 (HTTP + 경로) | 위임 서비스 |
|---|---|---|---|
JejuairSearchController | /internals/JEJUAIR/search | POST / (검색), GET / (상세), POST /reissue, GET /reissue | JejuairFlightSearchService, FlightAmenityService |
JejuairBookingController | /internals/JEJUAIR/bookings | POST / (예약), GET /{pnr}/expected-cancel, GET /{pnr}/cancelable, PUT /{pnr}/cancel, GET /{pnr} (조회), GET /{pnr}/confirm, GET /{pnr}/check-pnr, GET /{pnr}/repricing, POST /{pnr}/divide | JejuairBookingService, JejuairCancelService, JejuairFlightSearchService |
JejuairTicketingController | /internals/JEJUAIR/ticketing | POST / (발권), POST /addition (재발권, 202), GET /addition/{reissueKey} (폴링) | JejuairTicketingService, RedisTemplate |
JejuairFareRuleController | /internals/JEJUAIR/fare-rules | GET / (운임규정), GET /structured (구조화) | JejuairFareRuleService, JejuairFlightSearchService |
서킷브레이커는 검색에만
JejuairSearchController.search()에만@CircuitBreaker(name = "jejuairSearch", fallbackMethod = "searchFallback")가 걸려 있습니다(JejuairSearchController.kt:27). 열리면 fallback은 빈 리스트를 반환하고 Datadog 스팬에supplier.circuit-breaker=OPEN태그를 남깁니다(:94-100). 설정은application.yml:69의jejuairSearch: baseConfig: search. 예약/발권/취소엔 서킷브레이커가 없고 대신@Retryable로 보호합니다. 서킷브레이커·리트라이의 큰 그림은 resilience-and-events 참고.
재발권은 비동기 폴링(202 Accepted)
다른 오퍼레이션은 동기 200이지만, 재발권만
POST /addition이 즉시202 ACCEPTED + pollingKey를 반환하고 실제 작업은 Redis 폴링(polling/poller)으로 처리됩니다(JejuairTicketingController.kt:38-75). 발권 자체보다 처리 시간이 길고 차액 정산이 끼기 때문입니다. 상세는 jejuair-operations.
4.2 application 서비스 7종
| 서비스 | 책임 | 비동기/복원성 표식 |
|---|---|---|
JejuairFlightSearchService | 검색·검색상세·재발행검색·재발행상세 | withBlocking+pmap 병렬 |
JejuairPricingService | getTripSell로 운임 확정 + PssToken 획득 | (얇은 위임 계층) |
JejuairBookingService | 예약 생성/조회/확정/분리(divide) | withLaunch fire-and-forget |
JejuairTicketingService | 발권/재발권 + 발권실패 보상취소 | withLaunch+delay(5000) 취소 코루틴 |
JejuairCancelService | 취소가능 판정/취소(VOID·REFUND) | @Retryable(토큰 재발급 재시도) |
JejuairFareRuleService | 운임규정 조회(Redis 캐시) | withLaunch fire-and-forget |
지원 오퍼레이션 = Search · Booking · Ticketing · FareRule (4종)
상위 컨텍스트의 공식 오퍼레이션 목록과 일치합니다. tway/jinair에 있는 Ancillary·AgencyCredit 전용 컨트롤러는 jejuair에 없습니다(부가서비스는 예약 객체의
hasPaidAncillary플래그로 처리 차단 용도로만 등장). 이는 제주항공이 LCC지만 이 어댑터에서는 좌석/수하물 판매를 노출하지 않고 항공권 라이프사이클만 다룬다는 의미입니다.
5. 중요도 별점
중요도: ★★☆ (보통-상)
항목 평가 근거 코드 규모 보통 99파일/5,131줄 — 11개 중 10위(groupair 다음으로 작음) 비즈니스 중요도 높음 한국 1위 LCC, 한국발 단거리 국제선 핵심 재고 학습 가치 높음 GDS/NDC가 아닌 “항공사 직판 REST” 패턴의 대표 표본. PssToken 상태전달, RSA 카드암호화, 재발행 차액정산, 보상취소 코루틴을 한 모듈에서 응축해 볼 수 있음 위험도 보통-상 결제·재발행·환불을 직접 수행 → 차액 계산 오류·토큰 만료·취소 누락 시 금전 손실 위험 (jejuair-pitfalls) 온보딩 추천 순서: LCC REST 입문으로 적합. amadeus(GDS)·koreanair(NDC)의 무게에 비해 한 컨트롤러·한 클라이언트로 전체 흐름이 손에 잡혀 “공급사 모듈의 골격”을 배우기 좋습니다. 다만 결제/재발행 로직은 실수 시 비용이 크므로 jejuair-pitfalls를 반드시 같이 보세요.
다음으로 읽을 노트
- 제주항공 — 오퍼레이션 : 검색→가격확정→예약→발권→재발행→취소 시퀀스 디테일
- 제주항공 — 프로토콜 : PssToken 흐름, RSA 카드암호화, 채널/퍼널 헤더, JSON 봉투
- 제주항공 — 함정 : 토큰 만료 재시도, 재발행 차액 음수 차단, 부가서비스/체크인 시 취소 불가
- 시스템 아키텍처 · 요청 흐름 : 전체 그림