Amadeus NDC — 개요

module-amadeusndc arch-supplier pattern-ndc-order api-soap

한 줄 요약

Amadeus NDC(코드명 AMADEUSNDC)는 동일 항공사 GDS인 Amadeus(클래식 1A)별개의 독립 모듈로, Amadeus가 제공하는 NDC(New Distribution Capability) 표준 메시지(Travel_OfferPrice, Travel_OrderCreate, Travel_OrderPay …)를 SOAP/XML로 호출한다. 클래식 Amadeus가 PNR_AddMultiElements/Ticket_* 같은 EDIFACT 기반 stateful 세션을 쓰는 것과 달리, 이 모듈은 Order 중심(stateless) 의 NDC 오퍼레이션을 쓴다. 단, 검색(FareMasterPricer)과 큐(QCSDRQ 등)는 여전히 클래식 1A 메시지를 재사용하는 하이브리드 구조다.


1. 공급사 특징 — 왜 이렇게 연동하는가

1-1. LCC / FSC / GDS / NDC 분류 → NDC (GDS 채널 위에 얹힌 NDC)

AMADEUSNDC는 단일 항공사가 아니라 Amadeus가 중개하는 NDC 콘텐츠를 다룬다. 즉 GDS(Amadeus 1A) 인프라 위에서 항공사가 NDC로 직접 제공하는 운임/오더를 받는 채널이다. 코드로 확인되는 근거:

  • SOAP/XML 프로토콜: AmadeusndcClient@Qualifier("xmlMapper") ObjectMapper로 구성되고(AmadeusndcClient.kt:56-63), 모든 호출이 ClientSupport.execute<AmadeusndcResponse<T>>(soapBodyDeserializerOf(...)) 패턴으로 SOAP envelope를 파싱한다. LCC 모듈(tway/jinair/jejuair)의 JSON REST와 대조적이다.
  • NDC 표준 오퍼레이션 이름: 예약·결제·재발행 메시지가 전부 Amadeus의 NDC SOAP action 그대로다.
단계NDC 메시지 (SOAPAction)소스
검색FMPTBQ_23_1_1A (FareMasterPricer — 클래식 1A)request/faremasterpricertravelboardsearch/FareMasterPricerTravelBoardSearch.kt:37
가격확정Travel_OfferPrice_1.3request/offerprice/OfferPriceRQ.kt:19
예약생성Travel_OrderCreate_1.7request/ordercreate/OrderCreateRQ.kt:22
결제/발권Travel_OrderPay_1.7request/orderpay/OrderPayRQ.kt:22
조회Travel_OrderRetrieve_1.7request/orderretrieve/OrderRetrieveRQ.kt:19
변경(분리·재발행)Travel_OrderChange_1.9request/orderchange/OrderChangeRQ.kt:24
재발행검색·재가격Travel_OrderReshop_1.6request/orderreshop/OrderReshopRQ.kt:24
취소Travel_OrderCancel_1.0request/ordercancel/OrderCancelRQ.kt:19
QCSDRQ_13_1_1A / QDQLRQ_11_1_1A / QUQMUQ_03_1_1A / QUQMDQ_03_1_1A (클래식 1A)request/queue*/*.kt

"NDC인데 왜 검색·큐는 클래식 1A인가"

NDC는 운임·오더 라이프사이클(OfferPrice → OrderCreate → OrderPay → OrderChange/Reshop/Cancel)을 표준화한 것이고, 검색(FareMasterPricer)과 큐 관리(QCSDRQ 등)는 기존 1A 메시지를 그대로 재사용한다. 즉 이 모듈은 순수 NDC가 아니라 “클래식 1A 위에 NDC Order 메시지를 얹은 하이브리드”다. 클래식 Amadeus 모듈과 같은 TOPAS 인프라(AmadeusProperties)를 공유하는 이유이기도 하다. (상세는 amadeusndc-protocol)

1-2. “NDC ART 클라이언트” — 운임 규정만 별도 REST

오버라이드 컨텍스트가 말하는 “NDC ART 클라이언트”는 infrastructure/art/NdcArtClient.kt다. 운임 규정(FareRule)은 SOAP가 아니라 JSON REST로 별도 호출한다.

// NdcArtClient.kt:31-52
@Retryable(maxAttempts = 2)
fun getFareRules(adult, child, infant, fareItinerary): List<FareRule> {
    "${...art.endpoint}/api/v1/art/getrule/${...art.agentCode}/${...officeId}"
        .post(request)
        .header(mapOf(CONTENT_TYPE to APPLICATION_JSON_VALUE, "x-api-key" to ...art.apiKey))
        .execute<ArtResponse>()

이 클라이언트는 objectMapper(JSON)로 구성되고 x-api-key 헤더를 쓴다. SOAP AmadeusndcClient와는 완전히 다른 채널이다. ART 응답은 한국어로 가공된 룰("적용구간", "항공사 수수료", "수하물" …)을 정렬해서 내려준다(NdcArtClient.kt:62-83).

1-3. 세 번째 채널 — GPS(카드 승인) SOAP

발권/재발행 시 카드 결제 승인은 또 다른 SOAP 서비스 GPS_Approval_RequestService로 한다(infrastructure/gps/GpsClient.kt). 빈 이름이 @Component("amadeusndcGpsClient")로 한정되어, AmadeusndcTicketingService@Qualifier("amadeusndcGpsClient")로 주입받는다(AmadeusndcTicketingService.kt:26-27).

// GpsClient.kt:59-77 — 발권용 카드 승인
fun verifyCard(pnr, cardInfo: PaymentInfo.KeyInCard): VerifiedCardInfo
// GpsClient.kt:79-97 — 재발행용 카드 승인
fun verifyCard(pnr, cardInfo: CardInfo): VerifiedCardInfo

핵심: 이 모듈은 3종 프로토콜이 공존한다

“Amadeus NDC = SOAP” 라고 단정하면 안 된다. ① 메인 NDC/1A SOAP(AmadeusndcClient), ② 운임규정 JSON REST(NdcArtClient), ③ 카드승인 offline SOAP(GpsClient) 세 채널이 있다. 클래식 Amadeus의 3채널(메인 SOAP / ART / GPS)과 동일한 패턴이지만, 메인 채널의 메시지 체계가 EDIFACT가 아니라 NDC Order라는 점이 다르다.

1-4. NDC인데 왜 PNR과 발권이 따로인가 — 비즈니스 맥락

NDC는 항공사가 GDS 운임 외에 자사 직판 운임/부가서비스를 유통하기 위한 IATA 표준이다. Triple은 클래식 GDS 운임만으로 노출되지 않는 항공사 직판 운임을 잡기 위해 이 채널을 별도로 둔다. 코드에서 보이는 NDC 특유의 결정들:

  • Order(주문) 식별자 중심: 예약의 키가 supplierIdentificationKey(Order ID성격)이고, pnr은 별도. 조회는 retrieveByPnr 또는 retrieveBySupplierIdentificationKey 둘 다 존재(AmadeusndcClient.kt:284,306).
  • 발권은 현금/카드 단일 결제만 허용: AmadeusndcTicketingController.kt:50-56에서 cardInfo != null && cash > 0이면 INVALID_PARAMETER 예외. 주석 // ndcx 발권은 only card or only cash 만 가능이 명시.
  • 재발행 = OrderReshop(재가격) + OrderChange(확정): 클래식의 TST 재발행과 전혀 다른 NDC 2단계 흐름(reissueSearchpricingWithReissuereissue, AmadeusndcClient.kt:500,551,581).

2. 모듈 규모와 서브패키지 구조

2-1. 규모 (11개 공급사 중 4번째)

항목근거
.kt 파일 수357개find ... -name '*.kt' | wc -l
총 LOC12,444줄위와 동일
메인 클라이언트AmadeusndcClient.kt 865줄모듈 단일 최대 파일
11개 중 순위amadeus(873) > sabre(882) > galileo(525) > amadeusndc(357) > tway(253) …파일 수 기준 4위

파일 수의 대부분은 SOAP 스키마 DTO

357개 중 핵심 로직(controller 5 + application 6 + 주요 client 3)을 뺀 절대다수는 infrastructure/request(121개)·infrastructure/response(185개)의 SOAP/NDC XML 매핑 DTO다. 신입은 이 방대한 DTO 트리에 압도되지 말고 AmadeusndcClient + 6개 application 서비스부터 읽으면 된다.

2-2. 디렉터리 트리

supplier/amadeusndc
├── interfaces/controller/internals      # 공개 REST 컨트롤러 5개 (Triple 내부 API)
│   ├── AmadeusndcSearchController.kt
│   ├── AmadeusndcBookingController.kt
│   ├── AmadeusndcTicketingController.kt
│   ├── AmadeusndcFareRuleController.kt
│   └── AmadeusndcQueueController.kt
├── application                          # 비즈니스 서비스 6개
│   ├── AmadeusndcFlightSearchService.kt
│   ├── AmadeusndcBookingService.kt
│   ├── AmadeusndcCancelService.kt
│   ├── AmadeusndcTicketingService.kt
│   ├── AmadeusndcFareRuleService.kt
│   └── AmadeusndcQueueService.kt
├── infrastructure                       # 외부 API 클라이언트 + SOAP/NDC DTO
│   ├── AmadeusndcClient.kt              # 메인 NDC/1A SOAP 클라이언트 (865줄)
│   ├── Session.kt                       # 1A SOAP awsse:Session 매핑
│   ├── art/                             # NdcArtClient(JSON REST) + request/response
│   ├── gps/                             # GpsClient(카드승인 offline SOAP) + request/response
│   ├── request/                         # NDC 요청 DTO (offerprice, ordercreate, orderpay,
│   │                                    #   orderchange, orderreshop, ordercancel, orderretrieve,
│   │                                    #   queue*, faremasterpricertravelboardsearch)
│   └── response/                        # NDC 응답 DTO (offerprice, orderview, orderreshopreply,
│                                        #   ordercancel, queue*reply, faremasterpricer...reply)
├── domain
│   ├── model/AmadeusFlightSearch.kt     # FareItinerary 등 내부 도메인 모델
│   └── repository/                      # AmadeusndcBookingRepository, AmadeusndcFareItineraryRepository (Redis)
├── support
│   ├── model/                           # Booking, Passenger, Fare, OfferPriceInfo, QueuePnrInfo …
│   ├── enums/                           # PassengerTypeCode, CabinQualifier, AmadeusSoapHeaderNamespace …
│   └── util/                            # AirportUtils, FormatUtils
└── configuration
    └── RedisConfiguration.kt            # FareItinerary 전용 Gzip Redis 직렬화

레이어 흐름은 표준

interfaces(컨트롤러) → application(서비스) → infrastructure(클라이언트) → domain/support 순으로 흐른다. 전체 어댑터 공통 흐름은 request-flow, 11개 모듈이 어떻게 컨트롤러로 노출되는지는 system-architecture 참고.


3. 핵심 파일 표

파일 (모듈 루트 상대경로)역할메모
interfaces/controller/internals/AmadeusndcSearchController.kt검색/상세/재발행검색 REST@RequestMapping("/internals/AMADEUSNDC/search"), 유일하게 @CircuitBreaker("amadeusndcSearch") 보유
interfaces/controller/internals/AmadeusndcBookingController.kt예약/조회/취소/분리/repricing/internals/AMADEUSNDC/bookings
interfaces/controller/internals/AmadeusndcTicketingController.kt발권/재발행(폴링)/internals/AMADEUSNDC/ticketing, Redis 폴링 사용
interfaces/controller/internals/AmadeusndcFareRuleController.kt운임 규정 조회/internals/AMADEUSNDC/fare-rules
interfaces/controller/internals/AmadeusndcQueueController.kt큐 조회/제거/internals/AMADEUSNDC/queues
application/AmadeusndcFlightSearchService.kt검색·캐시·재발행검색·재발행상세pmap/withBlocking로 OD 조합 병렬 검색
application/AmadeusndcBookingService.kt예약 오케스트레이션(pricing→book→retrieve), 분리, 조회금액 불일치 시 비동기 취소
application/AmadeusndcCancelService.kt취소·취소가능 판정·비동기 취소cancelAsync가 코루틴 delay(5000) 후 취소
application/AmadeusndcTicketingService.kt발권(savePayment+티켓 폴링), 재발행GPS 카드승인·Slack 경보 호출
application/AmadeusndcFareRuleService.kt룰 캐시→ART REST 조회NdcArtClient 위임
application/AmadeusndcQueueService.kt큐 PNR 조회/제거(오프라인 OID)큐 번호 고정 "7"
infrastructure/AmadeusndcClient.kt메인 NDC/1A SOAP 클라이언트 (search, pricing, book, savePayment, retrieve, cancel, divide, reissue*, queue*)865줄, ClientSupport 상속, @Retryable 사용
infrastructure/art/NdcArtClient.kt운임규정 JSON RESTx-api-key, @Retryable(maxAttempts=2)
infrastructure/gps/GpsClient.kt카드 승인 offline SOAP@Component("amadeusndcGpsClient")
infrastructure/Session.kt1A SOAP awsse:Session 매핑TransactionStatusCode, SessionId, SecurityToken
domain/model/AmadeusFlightSearch.ktFareItinerary 등 내부 검색 도메인Serializable, Redis 캐시 대상
configuration/RedisConfiguration.ktFareItinerary 전용 Gzip Redis 템플릿AmadeusndcRedisConfiguration

4. 공개 인터페이스 (Triple 내부 API)

중앙 디스패처 없이 5개 컨트롤러가 직접 /internals/AMADEUSNDC/*로 노출된다(caller-callee-map 참고). 지원 오퍼레이션: Search, Booking, Ticketing, FareRule, Queue (※ CashReceipt 없음 — 클래식 Amadeus와의 차이).

4-1. 엔드포인트 전체 (검증된 @*Mapping)

오퍼레이션HTTP / 경로컨트롤러 메서드
SearchPOST /internals/AMADEUSNDC/searchAmadeusndcSearchController.search (@CircuitBreaker)
GET /internals/AMADEUSNDC/search.detail (key로 운임 상세 + amenity)
POST /internals/AMADEUSNDC/search/reissue.reissueSearch
GET /internals/AMADEUSNDC/search/reissue.reissueDetail
BookingPOST /internals/AMADEUSNDC/bookings.create (pricing→book→retrieve)
GET /internals/AMADEUSNDC/bookings/{pnr}.retrieve
PUT /internals/AMADEUSNDC/bookings/{pnr}/cancel.cancel
GET /internals/AMADEUSNDC/bookings/{pnr}/expected-cancel.expectedCancel
GET /internals/AMADEUSNDC/bookings/{pnr}/cancelable.cancelable
GET /internals/AMADEUSNDC/bookings/{pnr}/confirm.confirm (retrieveAndCancelIfNeed)
GET /internals/AMADEUSNDC/bookings/{pnr}/check-pnr.checkPnr (항상 true 반환 — 스텁)
GET /internals/AMADEUSNDC/bookings/{pnr}/repricing.repricing
POST /internals/AMADEUSNDC/bookings/{pnr}/divide.divide (PNR 분리, OrderChange)
TicketingPOST /internals/AMADEUSNDC/ticketing/ready.ready
POST /internals/AMADEUSNDC/ticketing.issue (현금 or 카드 단일)
POST /internals/AMADEUSNDC/ticketing/addition.reissue (202 ACCEPTED, Redis 폴링 키 반환)
GET /internals/AMADEUSNDC/ticketing/addition/{reissueKey}.checkReissue (폴링 결과 조회)
FareRuleGET /internals/AMADEUSNDC/fare-rules.getFareRules (ART REST)
GET /internals/AMADEUSNDC/fare-rules/structured.getStructuredFareRules
QueueGET /internals/AMADEUSNDC/queues.getQueues
PUT /internals/AMADEUSNDC/queues.remove

4-2. application 서비스 6종 (오퍼레이션 매핑)

서비스오퍼레이션핵심 책임
AmadeusndcFlightSearchServiceSearch캐시 조회→FareMasterPricer 병렬 검색→필터링→Redis 저장, 재발행검색(OrderReshop)
AmadeusndcBookingServiceBookingpricing(OfferPrice)book(OrderCreate)retrieveByPnr 오케스트레이션, divide, 금액 검증
AmadeusndcCancelServiceBooking(취소)cancelable 판정→cancel(OrderCancel), 코루틴 cancelAsync
AmadeusndcTicketingServiceTicketingsavePayment(OrderPay)+티켓 폴링, 재발행(OrderReshop+OrderChange), GPS 카드승인
AmadeusndcFareRuleServiceFareRule룰 캐시→NdcArtClient REST 위임
AmadeusndcQueueServiceQueue오프라인 OID로 큐 PNR 조회/제거

checkPnr는 무조건 true

AmadeusndcBookingController.kt:82-86checkPnr는 본문 없이 return true만 한다. 다른 모듈과 시그니처를 맞추기 위한 스텁이다. 실제 PNR 유효성 검증은 retrieve/confirm 경로에서 일어나므로, 이 엔드포인트 결과를 신뢰해 분기하면 안 된다. (지뢰: amadeusndc-pitfalls)


5. 중요도 별점

중요도: ★★★ (매우 높음)

근거

  • 규모 4위(357파일/12,444 LOC) 이자 메인 클라이언트가 865줄로 단일 최대 — 학습 분량이 크다.
  • 프로토콜 3종 공존(NDC/1A SOAP + ART JSON REST + GPS offline SOAP)으로 난이도가 클래식 Amadeus에 버금간다.
  • NDC Order 모델은 클래식 GDS의 PNR/TST 모델과 멘탈모델이 달라, Amadeus 모듈을 안다고 그대로 적용하면 오해가 생긴다.
  • 재발행이 가장 복잡한 시나리오 중 하나(OrderReshop 2단계 + 금액 감소/동일스케줄 검증 + Redis 폴링). 운영 이슈가 자주 닿는 영역.
  • 결제 제약(현금 XOR 카드), Slack 경보, 비동기 취소 등 운영성 로직이 application 레이어에 집중되어 신입이 반드시 이해해야 할 “왜”가 많다.

다만 클래식 Amadeus/Sabre보다 파일 수가 적고 stateful 세션의 복잡도가 없어 첫 GDS/NDC 모듈 학습 대상으로 적합하다.


다음에 읽을 노트