공통 오퍼레이션 생명주기
arch-overview pattern-strategy api-internals
이 노트의 목적
air-intl-adapter에는 11개 공급사 모듈이 있지만, 항공권을 팔기 위해 거치는 단계(오퍼레이션)는 공급사가 GDS든 NDC든 LCC든 사실상 동일하다. “검색 → 운임 재계산 → 예약 → 발권 → (취소/환불) → 사후처리”라는 비즈니스 흐름은 항공 산업 공통이기 때문이다. 이 시스템은 그 흐름을interfaces/request·interfaces/response패키지의 공통 DTO(계약) 로 못 박아 두고, 각 공급사 컨트롤러가 그 계약을 자기 방식으로 구현한다. 즉 “겉(계약)은 같고, 속(구현)만 다르다” — 이것이 어댑터 패턴의 핵심이다. 이 노트는 신입이 “한 번 흐름을 이해하면 11개 공급사 어디를 봐도 같은 지도를 쓸 수 있게” 만드는 것이 목표다.시스템 전체 그림은 system-architecture, 한 요청이 레이어를 통과하는 과정은 request-flow, DTO 계약의 필드 상세는 interfaces-dtos를 함께 보라.
1. 한눈에 보는 생명주기
항공권 1장이 팔리고 (필요시) 취소되기까지의 전형적 흐름이다. 호출자는 Triple 예약 시스템이고, 각 단계는 별도 HTTP 호출이다 (중앙 디스패처 없음 — system-architecture 참고).
flowchart TD T["Triple 예약 시스템"] T -->|"공급사를 알고 URL 경로로 직접 호출"| EP["/internals/{SUPPLIER}/... 공급사별 자체 컨트롤러"] subgraph SELL["조회/판매 흐름"] SEARCH["SEARCH<br/>POST /search<br/>key 발급"] FARERULE["FARE-RULE<br/>GET /fare-rules<br/>운임 규정 텍스트"] REPRICING["REPRICING<br/>GET /bookings/{pnr}/repricing<br/>또는 SEARCH의 detail (운임 재확인)"] BOOKING["BOOKING (예약 생성)<br/>POST /bookings<br/>PNR 발급"] READY["TICKETING-READY (발권 직전 재조회)<br/>POST /ticketing/ready"] TICKETING["TICKETING (발권 = 결제+발권)<br/>POST /ticketing<br/>티켓번호 발급"] CANCEL["CANCEL / REFUND (취소·환불)<br/>PUT /bookings/{pnr}/cancel<br/>+ cancelable / expected-cancel 사전조회"] SEARCH --> FARERULE FARERULE --> REPRICING SEARCH --> REPRICING REPRICING --> BOOKING BOOKING --> READY READY --> TICKETING TICKETING --> CANCEL end subgraph POST["사후 처리 흐름"] QUEUE["QUEUE<br/>예약 후 대기열 조회/제거<br/>GET PUT /queues"] CASH["CASH-RECEIPT<br/>현금영수증 발행/취소<br/>POST /cash-receipts/issue<br/>PUT /cash-receipts/cancel"] ANCILLARY["ANCILLARY (부가서비스)<br/>수하물/좌석 구매·해제<br/>GET POST DELETE /ancillaries"] AGENCY["AGENCY-CREDIT<br/>대리점 예치금 잔액 조회 (LCC)<br/>GET /agency-credit"] end EP --> SEARCH EP --> QUEUE
"key"와 "PNR"이 단계를 잇는 두 개의 끈
key: SEARCH가 발급하는 운임 식별자(예:{SUPPLIER}_{UUID}). FARE-RULE·detail·BOOKING이 이key로 “그때 그 운임”을 다시 찾는다.pnr(또는supplierIdentificationKey) : BOOKING이 발급하는 예약 식별자. REPRICING·READY·TICKETING·CANCEL·QUEUE·CASH-RECEIPT가 이pnr로 예약을 가리킨다. 신입이 단계 간 흐름을 추적할 때는 “이 단계의 입력 식별자가 key인가 pnr인가” 를 먼저 보면 어느 단계인지 바로 안다.
2. 각 단계의 의미 (신입 눈높이)
2-1. SEARCH (검색) — POST /internals/{SUPPLIER}/search
사용자가 “ICN→NRT, 6/10 출발, 성인 1명”을 입력하면 공급사에서 살 수 있는 운임 목록을 받아온다.
- 요청 DTO:
SearchRequest(interfaces/request/SearchRequest.kt:9) —BaseSearchRequest(BaseSearchRequest.kt:5) 인터페이스를 구현. 노선·여정은originDestinationLocationInfos: List<OriginDestinationLocationInfo>, 승객은preferences: List<SearchPreference>로 표현. - 응답 DTO:
List<FareItineraryView>(interfaces/response/FlightSearchView.kt:87) — 운임여정 요약 목록. - 상세 조회: 목록에서 하나를 고르면
GET /search(detail) →FareItineraryDetailView(FlightSearchView.kt:689)로 좌석/수하물/스케줄 상세를 받는다. - 반환 식별자:
FareItineraryView.key(FlightSearchView.kt:89, 형식{SUPPLIER}_{UUID})와itemKey({SUPPLIER}_{UUID}::{SHA3-256}).
// AmadeusSearchController.kt:25-26 — 검색 컨트롤러 진입
@CircuitBreaker(name = "amadeusSearch", fallbackMethod = "searchFallback")
@PostMapping
fun search(@RequestBody request: SearchRequest): ResponseEntity<List<FareItineraryView>> {검색은 공급사마다 "검색 가능 여부"를 먼저 거른다
SearchRequest.isSearchable(supplier)(SearchRequest.kt:26-34)가 공급사별 제약을 코드로 박아 둔다.
- TWAY/JINAIR: 편도·왕복(여정 ≤ 2)만, 항공사코드(
TW/LJ) 포함 시- SINGAPOREAIR: 항공사코드
SQ포함 시- GROUPAIR: 왕복(round-trip)일 때만 호출자가 “모든 공급사에 똑같이 뿌려도” 안 맞는 공급사는 스스로 빠진다. 이 분기를 모르면 “왜 T’way만 검색 결과가 없지?”에서 한참 헤맨다.
SEARCH만 서킷브레이커가 fallback으로 빈 목록을 반환한다
AmadeusSearchController.kt:73-79의searchFallback은 서킷이 OPEN이면emptyList()를 돌려준다. “한 공급사가 죽어도 검색 화면은 떠야 한다”는 설계. 예약/발권 같은 돈이 오가는 단계는 fallback으로 가짜 성공을 만들면 안 되므로 이 패턴을 쓰지 않는다. → resilience-and-events
2-2. FARE-RULE / REPRICING (운임 규정 / 운임 재계산)
이 둘은 “결제 전에 운임을 다시 한 번 확인”하는 단계로 자주 묶여서 다뤄지지만 목적이 다르다.
| 구분 | 무엇 | 입력 | 컨트롤러 예 | 응답 |
|---|---|---|---|---|
| FARE-RULE | 운임 규정(취소수수료·변경규정 등) 텍스트 | key (검색 운임) | AmadeusFareRuleController.kt:20 GET /fare-rules | List<FareRuleView> (FareRuleView.kt:6) + /structured → StructuredFareRuleView |
| REPRICING | 이미 만든 예약의 가격 재확인 | pnr (예약) | AmadeusBookingController.kt:129 GET /bookings/{pnr}/repricing | RepricingView (RepricingView.kt:13) |
- REPRICING의 의미: 예약(PNR)은 만들었지만 아직 발권 안 한 상태에서, 공급사에 “지금 이 예약 얼마야?”를 물어 최신 운임을 다시 받는다. 항공 운임은 시간/좌석에 따라 변하므로, 발권 직전 가격이 검색 시점과 다를 수 있다. → 정밀 분석은 마이그레이션 문서/request-flow 참고.
- REPRICING은 단순 retrieve가 아니다(공급사마다 다름):
- Amadeus/Sabre: 별도
bookingService.repricing(pnr)호출 (AmadeusBookingController.kt:131,SabreBookingController.kt:131). - Tway:
bookingService.confirmPrice(pnr)(TwayBookingController.kt:133). - Singaporeair/Jejuair/Jinair:
retrieve(pnr)의 승객 정보를 그대로RepricingView로 변환 (SingaporeairBookingController.kt:113,JejuairBookingController.kt:100,JinairBookingController.kt:114).
- Amadeus/Sabre: 별도
- REISSUE-SEARCH(재발행 검색): 발권된 티켓을 변경(재발행)하기 위한 검색.
POST /search/reissue+ReissueSearchRequest(ReissueSearchRequest.kt:5,BaseSearchRequest구현). amadeusndc·jejuair·jinair·koreanair·lufthansa·singaporeair·tway가 지원(아래 매트릭스).
REPRICING ≠ FARE-RULE ≠ REISSUE-SEARCH — 이름이 비슷해 헷갈린다
- FARE-RULE: “이 운임의 규정 문구” (텍스트)
- REPRICING: “내 예약의 현재 가격” (PNR 기준 금액)
- REISSUE-SEARCH: “발권 후 변경하려는데 새 운임” (재발행용 검색) 세 단어가 모두 “운임”을 다뤄서 신입이 자주 섞는다. 입력이
key면 FARE-RULE/REISSUE-SEARCH,pnr이면 REPRICING이라는 점을 기억하라.
2-3. BOOKING (예약 생성) — POST /internals/{SUPPLIER}/bookings
검색한 운임(
key)에 승객 정보를 붙여 PNR(예약번호)을 만든다. 아직 결제·발권은 안 한 상태.
- 요청 DTO:
BookingRequest(BookingRequest.kt:8) —key(검색 운임) +passengers: List<PassengerRequest>+ 예약자 연락처. - 응답 DTO:
BookingView(BookingView.kt:101) —pnr: PnrView(BookingView.kt:279), 스케줄, 승객,paymentTimeLimit(발권 마감시한) 등. - 컨트롤러 진입:
AmadeusBookingController.kt:25-38create().
/bookings 컨트롤러는 예약 생성 외에 예약 라이프사이클 전반을 담는다:
| 엔드포인트 | 메서드 | 의미 |
|---|---|---|
POST /bookings | create | 예약 생성(PNR 발급) |
GET /bookings/{pnr} | retrieve | 예약 조회 |
GET /bookings/{pnr}/confirm | confirm | 예약 확정/재조회 |
GET /bookings/{pnr}/check-pnr | checkPnr | PNR 유효성 확인 (Amadeus/Sabre/Singaporeair/Jejuair) |
GET /bookings/{pnr}/repricing | repricing | 운임 재계산(위 2-2) |
PUT /bookings/{pnr} | changeApis | 승객 여권/APIS 정보 변경 |
POST /bookings/{pnr}/divide | divide | PNR 분리(일부 승객만 떼어냄) |
PUT /bookings/{pnr}/cancel | cancel | 취소(아래 2-5) |
PNR이 곧 식별자, 단 NDC는
supplierIdentificationKey를 함께 쓴다GDS(Amadeus/Sabre)는 6자리 PNR이 전부지만, NDC 계열(KoreanAir 등)은 예약 식별을
supplierIdentificationKey(Order ID)로 한다.KoreanairBookingController.kt:69,103처럼@RequestParam supplierIdentificationKey를 필수로 받는 게 그 증거.PnrView(BookingView.kt:279-356)에supplierIdentificationKey필드가 있는 이유다. 같은 “예약 조회”라도 Amadeus는GET /bookings/{pnr}만으로 되지만 KoreanAir는 쿼리 파라미터가 더 필요하다.
2-4. TICKETING (발권) — POST /internals/{SUPPLIER}/ticketing
PNR을 실제 항공권(티켓번호)으로 만든다. 돈이 오가는 단계. 보통 2-step이다.
flowchart TD READY["① READY<br/>POST /ticketing/ready<br/>발권 직전 마지막 재조회 (승객·스케줄 최신화)"] ISSUE["② ISSUE<br/>POST /ticketing<br/>결제 + 발권 → TicketingView (티켓번호)"] READY --> ISSUE
- READY 요청/응답:
TicketingRequest(TicketingRequest.kt:9) →TicketingReadyView(TicketingReadyView.kt:18, 승객·스케줄). 예:AmadeusTicketingController.kt:19-29. - ISSUE 요청/응답:
TicketingRequest→TicketingView(TicketingView.kt:50, 발권된 승객별TicketView+PaymentView). 예:AmadeusTicketingController.kt:31-47. - 결제 정보:
TicketingRequest.paymentInfo는 sealed classPaymentInfoRequest(TicketingRequest.kt:43)로KEY_IN(카드 직접입력)·TOSS_PAY두 갈래.prepayment=true면 paymentInfo 없이도 허용(선결제) —TicketingRequest.kt:19-23init 블록이 이 불변식을 강제.
발권은 멱등하지 않다 — 두 번 부르면 중복 발권/중복 결제
READY와 ISSUE를 나눈 이유 중 하나가 “발권 직전 검증”이지만, ISSUE 자체는 재시도하면 위험하다. 그래서 발권 단계는 Resilience4j retry를 함부로 걸면 안 되는 대표 영역이다. 재발행(reissue)은 아예 비동기 폴링으로 뺀다(아래).
재발행(REISSUE/addition)은 코루틴+Redis 폴링 비동기다
발권 변경(재발행)은 시간이 오래 걸려 동기 응답이 어렵다. LCC들은
POST /ticketing/addition이 즉시202 ACCEPTED+DeferredKeyView(폴링키)를 주고, 호출자가GET /ticketing/addition/{reissueKey}로DeferredView(DeferredView.kt:5: Pending/Complete)를 폴링한다.
- 구현:
TwayTicketingController.kt:54-92,JinairTicketingController.kt:53-93,JejuairTicketingController.kt:38-75— 공통적으로polling(...)/poller<ReissueResult<...>>(...)유틸 +RedisTemplate사용.- 비동기 메커니즘 자체는 async-coroutines, 예외 전파는 error-handling 참고.
DeferredStatus.ERROR면 폴러가 저장된throwable을 다시 던진다(TwayTicketingController.kt:79).
2-5. CANCEL / REFUND (취소·환불)
발권 전이면 “예약 취소”, 발권 후면 “발권 취소(VOID) 또는 환불(REFUND)“로 갈린다. 취소는 항상 사전조회 → 실행 2-step을 권장.
flowchart TD PRE["cancelable / expected-cancel<br/>취소하면 얼마 돌려받고 수수료 얼마인지 미리 본다"] CANCEL["cancel<br/>실제 취소 실행 → CancelView (voided + refunds)"] PRE --> CANCEL
| 엔드포인트 | 메서드 | 응답 DTO | 의미 |
|---|---|---|---|
GET /bookings/{pnr}/cancelable | cancelable | CancelableTypeDetailView | 취소 가능 형태(VOID vs REFUND) + 예상 환불 |
GET /bookings/{pnr}/expected-cancel | expectedCancel | ExpectedCancelView (ExpectedCancelView.kt:9) | 예상 취소 결과(채널별 규칙 적용) |
PUT /bookings/{pnr}/cancel | cancel | CancelView (CancelView.kt:8) | 실제 취소. voided(무료취소 여부) + refunds: List<RefundView> |
- 요청 DTO:
CancelRequest(CancelRequest.kt:10) — 결제정보(환불 처리용)·autoRefundable·waivers(면제 사유).validate()(CancelRequest.kt:20)가 결제정보 조합(KeyInCard / 전부 null / TossPay)이 유효한지 검사. - VOID vs REFUND:
CancelView.voided(CancelView.kt:9)가 핵심. 발권 당일 취소면 VOID(무료, 환불내역 없음), 그 외는 REFUND(수수료 차감 후 환불).refunds는refundFee>0 || usedTax>0인 승객만 필터링해 담는다(CancelView.kt:18-20).
공급사마다 cancel의 "성공" 판정이 다르다
Amadeus:
CancelView.of(cancelDetail)로cancelDetail.action == VOID판정(CancelView.kt:34-42). Sabre: 서비스가 환불 목록을 반환하고refunds.isEmpty()이면 voided=true로 역산(SabreBookingController.kt:76-81). Singaporeair: 컨트롤러가 직접(voided, refunds)구조분해(SingaporeairBookingController.kt:43). 같은CancelView를 응답하지만 만드는 방식이 제각각이다 — 한 공급사 코드를 다른 공급사에 복붙하면 안 되는 대표 사례.
2-6. QUEUE (대기열) — GET/PUT /internals/{SUPPLIER}/queues
GDS의 “큐(Queue)“는 항공사·발권센터가 후속 작업을 쌓아두는 작업함이다. 어댑터는 큐에 쌓인 PNR을 조회하고, 처리 끝난 PNR을 큐에서 제거한다.
- 조회:
GET /queues→List<QueueView>(QueueView.kt:8). 예:AmadeusQueueController.kt:16-21. - 제거:
PUT /queues+List<QueueRemoveRequest>(QueueRemoveRequest.kt:3) →QueueRemoveView.AmadeusQueueController.kt:24-37은(queueNumber, category, timeMode)로 묶어 일괄 제거. - 지원: amadeus·amadeusndc·galileo·sabre (GDS/NDC-GDS 계열만). LCC·NDC 직판에는 큐 개념이 없다.
2-7. CASH-RECEIPT (현금영수증) — /internals/{SUPPLIER}/cash-receipts
한국 세법상 현금결제 시 발행하는 현금영수증. 발행·취소를 어댑터가 대행한다.
- 발행:
POST /cash-receipts/issue+CashReceiptRequest(CashReceiptRequest.kt:6) →CashReceiptView(CashReceiptView.kt:8, 승인번호·승인시각·금액·티켓번호목록). - 취소:
PUT /cash-receipts/cancel+CashReceiptCancelRequest(CashReceiptRequest.kt:14). 예:AmadeusCashReceiptController.kt. - 지원: amadeus·galileo·sabre.
2-8. ANCILLARY / AGENCY-CREDIT (부가서비스 / 대리점 크레딧) — LCC 중심
LCC(저비용항공)는 수하물·좌석을 별도 판매(ancillary)하고, 대리점 예치금(agency credit)으로 정산하는 경우가 많다.
- ANCILLARY:
/internals/{SUPPLIER}/ancillaries(jinair는/ancillary단수, 주의).- 가용 조회(
GET /ancillaries/avail/key|pnr), 수하물 조회/구매(GET /baggage/...,POST /baggage), 해제(DELETE /ancillaries), 딥링크(POST /.../deep-link). - 요청 DTO:
BaggageRequest/AncillaryReleaseRequest/AncillaryDeepLinkRequest/AncillaryPurchaseRequest(AncillaryRequest.kt). 예:TwayAncillaryController.kt. - 지원: tway·jinair·lufthansa·singaporeair (NDC도 부가서비스가 있어 lufthansa/singaporeair 포함).
- 가용 조회(
- AGENCY-CREDIT:
GET /internals/{SUPPLIER}/agency-credit→AgencyCreditView(amount)(AgencyCreditView.kt:3). 대리점 잔액 조회. 지원: tway·jinair. 예:TwayAgencyCreditController.kt.
3. 공통 DTO가 정의하는 “계약” — 어댑터의 심장
핵심 통찰: 계약(DTO)은 1벌, 구현(
.of())은 11벌
interfaces/request·interfaces/response의 DTO는 공급사 중립적이다. Triple은 이 1벌의 계약만 알면 된다. 공급사 차이는 전부 응답 DTO의companion object { fun of(...) }팩토리 오버로딩으로 흡수된다.가장 극적인 예가
FlightSearchView.kt다. 한 파일 상단(FlightSearchView.kt:10-86)에 11개 공급사의domain.model.FareItinerary를 각각 alias import하고,FareItineraryView.of(...)를 공급사 타입마다 오버로드한다.FlightSearchView.of(fareItinerary: AmadeusFareItinerary)(:104),of(fareItinerary: GalileoFareItinerary)(:230),of(combinedFareItinerary: Pair<TwayFareItinerary, ...>)(:142) … 입력 타입만 다르고 출력은 모두 같은FareItineraryView다.
// FlightSearchView.kt:142-167 — Tway는 출발/귀국편을 Pair로 결합해 하나의 View로 만든다(LCC 특성)
fun of(
combinedFareItinerary: Pair<TwayFareItinerary, TwayFareItinerary?>,
tripDirectionType: TripDirectionType? = null,
): FareItineraryView { ... } // ← GDS는 단일 itinerary, LCC는 편별 결합. 같은 View, 다른 조립.단계별 공통 DTO 빠른 표
| 단계 | 요청 DTO | 응답 DTO | 정의 파일 |
|---|---|---|---|
| Search | SearchRequest / SearchDetailRequest | FareItineraryView / FareItineraryDetailView | SearchRequest.kt, FlightSearchView.kt |
| Reissue-Search | ReissueSearchRequest | FareItineraryView | ReissueSearchRequest.kt, FlightSearchView.kt |
| Fare-Rule | (쿼리: key, adult/child/infant) | FareRuleView / StructuredFareRuleView | FareRuleView.kt, StructuredFareRuleView.kt |
| Repricing | (path: pnr) | RepricingView | RepricingView.kt |
| Booking | BookingRequest / BookingChangeRequest / BookingDivideRequest | BookingView / BookingChangeView | BookingRequest.kt, BookingView.kt |
| Ticketing-Ready | TicketingRequest | TicketingReadyView | TicketingRequest.kt, TicketingReadyView.kt |
| Ticketing-Issue | TicketingRequest | TicketingView | TicketingRequest.kt, TicketingView.kt |
| Reissue(addition) | ReticketingRequest | DeferredKeyView → DeferredView<ReticketingView> | TicketingRequest.kt, DeferredView.kt, ReticketingView.kt |
| Cancel | CancelRequest | CancelView / CancelableTypeDetailView / ExpectedCancelView | CancelRequest.kt, CancelView.kt, ExpectedCancelView.kt |
| Refund(내역) | — | RefundView | RefundView.kt |
| Queue | QueueRemoveRequest | QueueView / QueueRemoveView | QueueRemoveRequest.kt, QueueView.kt |
| Cash-Receipt | CashReceiptRequest / CashReceiptCancelRequest | CashReceiptView | CashReceiptRequest.kt, CashReceiptView.kt |
| Ancillary | BaggageRequest / AncillaryReleaseRequest / AncillaryDeepLinkRequest / AncillaryPurchaseRequest | AncillaryView 등 | AncillaryRequest.kt, AncillaryView.kt |
| Agency-Credit | — | AgencyCreditView | AgencyCreditView.kt |
필드 단위 상세(예:
FareView의qCharge,TicketView의conjunctionTicketNumber)는 interfaces-dtos에서 다룬다.
4. 공급사 × 오퍼레이션 지원 매트릭스 (컨트롤러 실재 기준)
이 표는 추측이 아니라
interfaces/controller/internals/*Controller.kt의 실제 존재 여부로 작성됨근거: 전체 컨트롤러 목록과 각
@RequestMapping경로를 스캔(find ... *Controller.kt).
- O = 해당 오퍼레이션 컨트롤러/엔드포인트 존재, ─ = 없음
| 공급사 | 유형 | Search | Reissue-Search | FareRule | Repricing | Booking | Ticketing | Reissue(addition) | Cancel/Refund | Queue | CashReceipt | Ancillary | AgencyCredit |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| amadeus | GDS(1A) | O | ─ | O | O | O | O | ─ | O | O | O | ─ | ─ |
| sabre | GDS(1S) | O | ─ | O | O | O | O | ─ | O | O | O | ─ | ─ |
| galileo | GDS(1G) | O | ─ | O | O | O | O | ─ | O | O | O | ─ | ─ |
| amadeusndc | NDC | O | O | O | O | O | O | ─ | O | O | ─ | ─ | ─ |
| koreanair | NDC | O | O | O | ─ | O | O | ─ | O | ─ | ─ | ─ | ─ |
| lufthansa | NDC | O | O | O | O | O | O | ─ | O | ─ | ─ | O | ─ |
| singaporeair | NDC | O | O | O | O | O | O | ─ | O | ─ | ─ | O | ─ |
| tway | LCC | O | O | O | O | O | O | O | O | ─ | ─ | O | O |
| jinair | LCC | O | O | O | O | O | O | O | O | ─ | ─ | O | O |
| jejuair | LCC | O | O | O | O | O | O | O | O | ─ | ─ | ─ | ─ |
| groupair | 그룹 | O | ─ | O | ─ | O | O | ─ | O(취소만) | ─ | ─ | ─ | ─ |
표를 읽는 법 / 주의점
- Search·FareRule·Booking·Ticketing·Cancel은 11개 전부 지원 = 이것이 “공통 핵심 오퍼레이션”이다. 신입은 이 5개만 먼저 이해해도 어느 공급사든 큰 그림을 잡는다.
- Queue/CashReceipt = GDS 전유물. amadeus·sabre·galileo만 가짐(amadeusndc는 Queue만). LCC/NDC직판엔 큐·현금영수증 개념이 없다.
- Reissue(addition, 비동기 발권변경) = LCC 전유물. tway·jinair·jejuair만
POST /ticketing/addition을 가짐. → async-coroutines- Ancillary/AgencyCredit = LCC 중심(단 Ancillary는 NDC인 lufthansa·singaporeair도 보유).
- groupair는 가장 작은 모듈: repricing·divide 엔드포인트가 없고 cancel은 있지만 cancelable까지만(취소/조회 위주). 단체운임 특성상 개별 가격 재계산을 하지 않는다 —
GroupairBookingController.kt에 repricing/divide 부재 확인.- koreanair에는 repricing 엔드포인트가 없다(
GET /bookings/{pnr}/repricing부재). NDC Order 모델에서 재계산을 다르게 처리.
5. 신입이 자주 빠지는 함정 (요약)
핵심 함정 모음
- “같은 URL = 같은 동작”이 아니다.
GET /bookings/{pnr}도 Amadeus는 pnr만, KoreanAir는supplierIdentificationKey쿼리 필수.- repricing이 retrieve인 공급사와 별도 호출인 공급사가 섞여 있다(2-2 표). 응답이 같아 보여도 비용·부작용이 다르다.
- 발권(ISSUE)·재발행은 멱등하지 않다. 재시도/리트라이를 무심코 걸면 중복 결제. → resilience-and-events
- 재발행은 비동기 폴링이라 즉시 응답(202)을 “실패”로 오해하기 쉽다.
DeferredView.Pending이 정상 상태다.- 공급사별 “검색 가능 조건”(
isSearchable,SearchRequest.kt:26)을 모르면 “결과 없음”의 원인을 못 찾는다./ancillaries(복수) vs jinair/ancillary(단수) 경로 표기가 다르다.
세부 지뢰는 landmines, 디버깅 연습은 exercises-debugging, 단계별 더 깊은 코드 추적은 각 공급사의 *-operations 노트로 이어진다.
6. 더 깊이 — 대표 공급사 오퍼레이션 노트로
- GDS 대표 (세션/SOAP, 가장 복잡): amadeus-operations, sabre-operations
- NDC 대표 (Order 모델): koreanair-operations, singaporeair-operations
- LCC 대표 (REST + 비동기 재발행): tway-operations, jinair-operations
- 외부 API 원형 분석(레거시 참조): amadeus-gds, sabre-gds, galileo-gds, singaporeair-ndc
학습 순서 추천
① 이 노트로 흐름 골격 → ② interfaces-dtos로 계약 필드 → ③ request-flow로 한 요청의 레이어 통과 → ④ 관심 공급사 *-operations로 구현 디테일. 온보딩 전체 지도는 onboarding-map, 빠른 참조는 quick-reference.