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.3 | request/offerprice/OfferPriceRQ.kt:19 |
| 예약생성 | Travel_OrderCreate_1.7 | request/ordercreate/OrderCreateRQ.kt:22 |
| 결제/발권 | Travel_OrderPay_1.7 | request/orderpay/OrderPayRQ.kt:22 |
| 조회 | Travel_OrderRetrieve_1.7 | request/orderretrieve/OrderRetrieveRQ.kt:19 |
| 변경(분리·재발행) | Travel_OrderChange_1.9 | request/orderchange/OrderChangeRQ.kt:24 |
| 재발행검색·재가격 | Travel_OrderReshop_1.6 | request/orderreshop/OrderReshopRQ.kt:24 |
| 취소 | Travel_OrderCancel_1.0 | request/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단계 흐름(
reissueSearch→pricingWithReissue→reissue,AmadeusndcClient.kt:500,551,581).
2. 모듈 규모와 서브패키지 구조
2-1. 규모 (11개 공급사 중 4번째)
| 항목 | 값 | 근거 |
|---|---|---|
.kt 파일 수 | 357개 | find ... -name '*.kt' | wc -l |
| 총 LOC | 약 12,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 REST | x-api-key, @Retryable(maxAttempts=2) |
infrastructure/gps/GpsClient.kt | 카드 승인 offline SOAP | @Component("amadeusndcGpsClient") |
infrastructure/Session.kt | 1A SOAP awsse:Session 매핑 | TransactionStatusCode, SessionId, SecurityToken |
domain/model/AmadeusFlightSearch.kt | FareItinerary 등 내부 검색 도메인 | Serializable, Redis 캐시 대상 |
configuration/RedisConfiguration.kt | FareItinerary 전용 Gzip Redis 템플릿 | AmadeusndcRedisConfiguration |
4. 공개 인터페이스 (Triple 내부 API)
중앙 디스패처 없이 5개 컨트롤러가 직접 /internals/AMADEUSNDC/*로 노출된다(caller-callee-map 참고). 지원 오퍼레이션: Search, Booking, Ticketing, FareRule, Queue (※ CashReceipt 없음 — 클래식 Amadeus와의 차이).
4-1. 엔드포인트 전체 (검증된 @*Mapping)
| 오퍼레이션 | HTTP / 경로 | 컨트롤러 메서드 |
|---|---|---|
| Search | POST /internals/AMADEUSNDC/search | AmadeusndcSearchController.search (@CircuitBreaker) |
GET /internals/AMADEUSNDC/search | .detail (key로 운임 상세 + amenity) | |
POST /internals/AMADEUSNDC/search/reissue | .reissueSearch | |
GET /internals/AMADEUSNDC/search/reissue | .reissueDetail | |
| Booking | POST /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) | |
| Ticketing | POST /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 (폴링 결과 조회) | |
| FareRule | GET /internals/AMADEUSNDC/fare-rules | .getFareRules (ART REST) |
GET /internals/AMADEUSNDC/fare-rules/structured | .getStructuredFareRules | |
| Queue | GET /internals/AMADEUSNDC/queues | .getQueues |
PUT /internals/AMADEUSNDC/queues | .remove |
4-2. application 서비스 6종 (오퍼레이션 매핑)
| 서비스 | 오퍼레이션 | 핵심 책임 |
|---|---|---|
AmadeusndcFlightSearchService | Search | 캐시 조회→FareMasterPricer 병렬 검색→필터링→Redis 저장, 재발행검색(OrderReshop) |
AmadeusndcBookingService | Booking | pricing(OfferPrice)→book(OrderCreate)→retrieveByPnr 오케스트레이션, divide, 금액 검증 |
AmadeusndcCancelService | Booking(취소) | cancelable 판정→cancel(OrderCancel), 코루틴 cancelAsync |
AmadeusndcTicketingService | Ticketing | savePayment(OrderPay)+티켓 폴링, 재발행(OrderReshop+OrderChange), GPS 카드승인 |
AmadeusndcFareRuleService | FareRule | 룰 캐시→NdcArtClient REST 위임 |
AmadeusndcQueueService | Queue | 오프라인 OID로 큐 PNR 조회/제거 |
checkPnr는 무조건true
AmadeusndcBookingController.kt:82-86의checkPnr는 본문 없이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 모듈을 안다고 그대로 적용하면 오해가 생긴다.
- 재발행이 가장 복잡한 시나리오 중 하나(
OrderReshop2단계 + 금액 감소/동일스케줄 검증 + Redis 폴링). 운영 이슈가 자주 닿는 영역.- 결제 제약(현금 XOR 카드), Slack 경보, 비동기 취소 등 운영성 로직이 application 레이어에 집중되어 신입이 반드시 이해해야 할 “왜”가 많다.
다만 클래식 Amadeus/Sabre보다 파일 수가 적고 stateful 세션의 복잡도가 없어 첫 GDS/NDC 모듈 학습 대상으로 적합하다.