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.0infrastructure/JejuairClient.ktHTTP POST + JSON, SOAP 아님
MediaType.APPLICATION_JSON_VALUE 헤더JejuairClient.kt:53-54Content-Type/Accept 모두 JSON
OkHttp 기반 ClientSupport 상속JejuairClient.kt:42GDS SOAP 스택과 무관한 순수 HTTP 클라이언트
JejuairResponse<T> 제네릭 래퍼infrastructure/response/JejuairResponse.ktJSON 응답 공통 봉투 + 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/FareRuleServiceCoroutineScope(Dispatchers.IO).withLaunch { ... } — SOLD_OUT 시 검색키 제거 + 미노출 운임 저장.

비동기 공통 유틸은 async-coroutines 참고. withLaunch/withBlocking/pmapsupport/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결제 카드 DTO8개 필드에 @Encrypt(cipher = JejuairCipher::class)
domain/model/FareItinerary.kt검색결과 도메인 + 캐시 키id=SHA3 해시, itemKey, validate()(child/infant 운임 존재 검증)
support/model/Booking.kt예약 도메인isVoidable(당일발권), nonTicketing, groupedSchedules, 재발행 차액 계산
configuration/RedisConfiguration.ktRedis 직렬화GzipRedisSerializer + Jackson, FareItinerary 전용 템플릿

4. 공개 인터페이스 (컨트롤러 → application)

중앙 디스패처 없음

이 시스템은 중앙 라우터가 없습니다. 공급사 컨트롤러 자체가 Triple 예약 시스템의 내부 API로 노출됩니다(경로 prefix /internals/JEJUAIR/...). 호출 관계 전체 지도는 caller-callee-map 참고.

4.1 컨트롤러 4종과 엔드포인트

컨트롤러@RequestMapping엔드포인트 (HTTP + 경로)위임 서비스
JejuairSearchController/internals/JEJUAIR/searchPOST / (검색), GET / (상세), POST /reissue, GET /reissueJejuairFlightSearchService, FlightAmenityService
JejuairBookingController/internals/JEJUAIR/bookingsPOST / (예약), GET /{pnr}/expected-cancel, GET /{pnr}/cancelable, PUT /{pnr}/cancel, GET /{pnr} (조회), GET /{pnr}/confirm, GET /{pnr}/check-pnr, GET /{pnr}/repricing, POST /{pnr}/divideJejuairBookingService, JejuairCancelService, JejuairFlightSearchService
JejuairTicketingController/internals/JEJUAIR/ticketingPOST / (발권), POST /addition (재발권, 202), GET /addition/{reissueKey} (폴링)JejuairTicketingService, RedisTemplate
JejuairFareRuleController/internals/JEJUAIR/fare-rulesGET / (운임규정), GET /structured (구조화)JejuairFareRuleService, JejuairFlightSearchService

서킷브레이커는 검색에만

JejuairSearchController.search()에만 @CircuitBreaker(name = "jejuairSearch", fallbackMethod = "searchFallback")가 걸려 있습니다(JejuairSearchController.kt:27). 열리면 fallback은 빈 리스트를 반환하고 Datadog 스팬에 supplier.circuit-breaker=OPEN 태그를 남깁니다(:94-100). 설정은 application.yml:69jejuairSearch: 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 병렬
JejuairPricingServicegetTripSell로 운임 확정 + 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를 반드시 같이 보세요.


다음으로 읽을 노트