Amadeus — 개요

module-amadeus arch-supplier pattern-stateful-session api-soap

한 줄 요약

Amadeus는 어댑터에서 가장 큰 모듈(약 3.9MB, 873개 .kt 중 794개가 infrastructure/) 이며, 국내 TOPAS(1A) 호스트를 경유해 Amadeus GDS에 SOAP로 연결한다. 다른 어떤 공급사와도 다른 결정적 특징은 stateful PNR 세션으로, 하나의 예약을 위해 여러 SOAP 호출이 하나의 트랜잭션 세션(Start → InSeries → End)으로 묶인다.


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

1-1. LCC / FSC / GDS 분류 → GDS

Amadeus는 항공사(LCC/FSC)가 아니라 GDS(Global Distribution System) 다. 즉 단일 항공사 REST API가 아니라, 수많은 항공사 재고를 중개하는 시스템에 연결한다. 코드로 보이는 GDS의 흔적:

  • SOAP/XML 프로토콜: AmadeusClient@Qualifier("xmlMapper") ObjectMapper로 구성되고, 모든 호출이 ClientSupport.execute<AmadeusResponse<T>>(soapBodyDeserializerOf(...)) 패턴으로 SOAP envelope를 파싱한다. LCC 모듈(tway/jinair/jejuair)이 JSON REST를 쓰는 것과 대조적이다.
    • AmadeusClient.kt:96-110soapBodyDeserializerOfsoap(content).soapBody(mapper)로 SOAP body를 역직렬화하고, 동시에 SOAP headerawsse:Session을 추출한다.
  • GDS 표준 오퍼레이션 이름: 메시지명이 전부 Amadeus EDIFACT/SOAP 표준 그대로다 — FareMasterPricerTravelBoardSearch(검색), AirSellFromRecommendation(좌석 점유), PNR_AddMultiElements(PNR 요소 추가), Fare_PricePNRWithBookingClass(운임 계산), Ticket_CreateTSTFromPricing(TST 생성), DocIssuance_IssueTicket(발권), DocRefund_*(환불) 등.

1-2. 국내 TOPAS(1A) 경유

Amadeus의 한국 내 호스트는 TOPAS(1A 시스템)다. 이 모듈은 Amadeus 본사 API가 아니라 TOPAS가 제공하는 3개의 채널로 분리되어 붙는다(infrastructure/topas/ 디렉터리가 그 증거):

채널클래스프로토콜용도
메인 GDS SOAPinfrastructure/AmadeusClient.ktSOAP/XML (stateful)Search·Booking·Ticketing·Queue·Refund 등 핵심 전부
ARTinfrastructure/topas/ArtClient.ktJSON REST (x-api-key)FareRule 운임 규정 조회 (/api/v1/art/getrule/...)
GPSinfrastructure/topas/GpsClient.ktoffline SOAP (stateless)카드 승인/취소(approve/cancel)·현금영수증(issueCashReceipt)

왜 ART/GPS만 따로 떼어냈나

운임 규정(FareRule)은 한국어로 가공된 룰을 TOPAS ART가 JSON으로 주므로 GDS SOAP과 별도 클라이언트(ArtClient)다. 카드 승인·현금영수증은 한국 결제 환경 전용 오프라인 서비스(GPS_Approval_RequestService)라 별도 endpoint(GpsClient)다. 즉 “Amadeus = SOAP” 라고 단정하면 안 되고, 3종 프로토콜이 공존한다. (amadeus-protocol에서 상세)

1-3. 세션/인증 상태 보유 (stateful) — 이 모듈의 핵심

대부분 LCC 어댑터는 매 호출이 독립적(stateless)이다. 반면 Amadeus는 하나의 PNR 작업이 SOAP 세션 안에서 여러 메시지로 이어진다.

  • support/util/StatefulBuilder.ktSession?을 들고 다니는 빌더. start{}TransactionStatusCode.Start, inSeries{}InSeries(sequenceNumber+1), end{}End로 세션 상태를 전이시킨다.
  • infrastructure/Session.kt — SOAP awsse:Session 헤더에 매핑되는 sessionId, sequenceNumber, securityToken.
  • support/enums/TransactionStatusCode.ktStart / InSeries / End 3값.
// AmadeusBookingService.book() 의 골격 — 한 예약 = 한 세션 (AmadeusBookingService.kt:65~166)
stateful {
    start    { amadeusClient.markSeat(...) }              // 좌석 점유, 세션 시작
    inSeries { amadeusClient.saveReservationInfo(...) }   // 승객/여정 추가
    pricingService.pricing(...)                            // 운임 계산
    inSeries { amadeusClient.getPnrFares(...) }
    inSeries { amadeusClient.savePnrWithShowWarnings(...) } // EOT(End Of Transaction) → PNR 채번
    ...
    end      { amadeusClient.signOut(...) }                // 세션 종료
}

세션 토큰은 매 응답의 SOAP 헤더에서 받아 statefulBuilder?.receiveSession(response.session)으로 갱신되고, 다음 요청 빌드 시 request.withSession(statefulBuilder?.session)으로 재주입된다. SOAP envelope 생성부(AmadeusClient.kt:1327~1417, soapRequestBodyConverter)에서 첫 호출(isStart)에만 WS-Security UsernameToken(nonce + PasswordDigest)과 AMA_SecurityHostedUser를 붙이고, 이후 호출은 Session 헤더만 실어 보낸다.

세션 누수 = 라이선스 자원 고갈

모든 book/confirm/repricing/divide/retrievecatch (e: Exception)에서 session?.transactionStatusCode == InSeries면 강제로 end { signOut() }을 호출한다(AmadeusBookingService.kt:171-181 등). 세션을 닫지 않으면 TOPAS 쪽 동시 세션 한도를 소진한다. 신규 오퍼레이션을 추가할 때 이 finally-signout 패턴을 반드시 따라야 한다. → amadeus-pitfalls

1-4. 비즈니스/시장 맥락

Amadeus(1A)는 전 세계 FSC(대형 항공사) 재고의 표준 GDS다. Triple 예약 시스템 입장에서 Amadeus 모듈은 NDC 미연동 항공사·다구간/SOTO 운임을 포괄하는 가장 광범위한 공급 채널이라 기능 폭(Search→Ticketing→Refund→Queue→CashReceipt)이 가장 넓고, 그래서 모듈도 가장 크다.


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

분석 루트: src/main/kotlin/com/triple/air/intl/adapter/supplier/amadeus

서브패키지.kt 파일 수역할
interfaces/6Triple 내부 API로 노출되는 6개 REST 컨트롤러 (controller/internals/)
application/11오케스트레이션 서비스(세션 흐름·재시도·Slack 경보 조립)
infrastructure/794SOAP/REST 클라이언트 + 오퍼레이션별 request/response DTO(대부분 스키마 매핑 클래스)
support/58enums/(40+), model/(도메인 표현), util/(StatefulBuilder 등)
domain/3model/FareItinerary, repository/(Redis 캐시 repo 2종)
configuration/1AmadeusRedisConfiguration(FareItinerary·InitRefund Redis 템플릿)
  • 모듈 합계 873개 .kt 파일, 약 3.9MB → 11개 공급사 중 최대. infrastructure/만 794개로, 그중 infrastructure/{operation}/ 24개 서브폴더(각각 pnraddmultielements, faremasterpricertravelboardsearch, docrefund* 등)에 SOAP 메시지 스키마 DTO가 펼쳐져 있다. 즉 크기의 본질은 GDS SOAP 스키마의 방대함이지 비즈니스 로직 양이 아니다.
supplier/amadeus
├── interfaces/controller/internals/   ← 6 컨트롤러 (Triple 내부 API 표면)
├── application/                        ← 11 서비스 (세션 오케스트레이션)
├── infrastructure/
│   ├── AmadeusClient.kt                ← 메인 GDS SOAP 클라이언트 (1426 LOC)
│   ├── AmadeusResponse.kt / Session.kt / AmadeusKeyInError.kt
│   ├── request/  (24 오퍼레이션 폴더 + AmadeusRequest.kt)
│   ├── response/ (오퍼레이션별 *Reply 폴더)
│   └── topas/    ← ArtClient(REST) · GpsClient(offline SOAP) + Art/Gps Request·Response
├── support/{enums(40+), model, util(StatefulBuilder)}
├── domain/{model/FareItinerary, repository(Redis)}
└── configuration/AmadeusRedisConfiguration.kt

3. 핵심 파일 표

파일역할메모
infrastructure/AmadeusClient.kt메인 GDS SOAP 클라이언트. 검색·예약·발권·환불·큐 등 모든 SOAP 메시지를 메서드로 노출1426 LOC. soapRequestBodyConverter(WS-Security/Session 헤더 빌드), soapBodyDeserializerOf(세션+body 파싱) 포함
infrastructure/Session.ktSOAP awsse:Session 헤더 ↔ Kotlin 데이터 클래스sessionId/sequenceNumber/securityToken
support/util/StatefulBuilder.kt세션 상태 전이기. stateful{}/start{}/inSeries{}/end{} 확장함수이 모듈을 이해하는 출발점
infrastructure/AmadeusResponse.ktAmadeusResponse<T>(session, body) 래퍼모든 응답이 세션+body 쌍
infrastructure/AmadeusKeyInError.kt카드 키인 결제 에러코드 → 사용자 메시지 매핑approveByKeyIn에서 사용
infrastructure/topas/ArtClient.ktTOPAS ART 운임규정 REST 클라이언트JSON, x-api-key, @Retryable(maxAttempts=2)
infrastructure/topas/GpsClient.ktTOPAS GPS 오프라인 SOAP (카드 승인/취소·현금영수증)stateless, /GPS_Approval_RequestService
support/enums/AmadeusSoapHeaderNamespace.ktWS-Addressing·WS-Security·Session 네임스페이스 정의SOAP 헤더 빌드의 상수 출처
domain/model/AmadeusFlightSearch.kt + domain/repository/AmadeusFareItineraryRepository.kt검색 결과(FareItinerary) Redis 캐시검색→상세→예약 간 키 전달
configuration/RedisConfiguration.ktAmadeusRedisConfiguration — FareItinerary/InitRefund Gzip Redis 템플릿
configuration/Properties.kt(공통, ..adapter/configuration/Properties.kt:23-88)AmadeusApiProperties(officeId·iataCode·userName·password·endpoint·art)·AmadeusProperties(채널/퍼널/gps·changedDate 분기)secret은 AWS Secrets Manager(supplier/amadeus.yml)에서 로드

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

4-1. 컨트롤러 → 엔드포인트

모든 컨트롤러는 @RestController + /internals/AMADEUS/... 경로. 중앙 디스패처 없이 공급사별 컨트롤러가 직접 노출된다(어댑터 전체 패턴 → system-architecture, request-flow).

컨트롤러베이스 경로엔드포인트 (@*Mapping)호출 서비스
AmadeusSearchController/internals/AMADEUS/searchPOST / (검색, @CircuitBreaker("amadeusSearch"))
GET / (상세 detail)
AmadeusFlightSearchService, FlightAmenityService
AmadeusBookingController/internals/AMADEUS/bookingsPOST / (create)
PUT /{pnr} (changeApis)
PUT /{pnr}/cancel
GET /{pnr}/expected-cancel
GET /{pnr}/cancelable
GET /{pnr} (retrieve)
GET /{pnr}/check-pnr
GET /{pnr}/confirm
GET /{pnr}/repricing
POST /{pnr}/divide
AmadeusBookingService, AmadeusPassengerService, AmadeusCancelService
AmadeusTicketingController/internals/AMADEUS/ticketingPOST /ready
POST / (issue 발권)
AmadeusTicketingService
AmadeusFareRuleController/internals/AMADEUS/fare-rulesGET / (운임규정)
GET /structured (구조화 규정)
AmadeusFareRuleService, AmadeusFlightSearchService
AmadeusQueueController/internals/AMADEUS/queuesGET / (큐 PNR 목록)
PUT / (remove)
AmadeusQueueService
AmadeusCashReceiptController/internals/AMADEUS/cash-receiptsPOST /issue
PUT /cancel
AmadeusCashReceiptService

6 컨트롤러 ↔ 6 오퍼레이션 ↔ 11 서비스

오퍼레이션은 6개([Search, Booking, Ticketing, FareRule, Queue, CashReceipt])지만 application 서비스는 11개다. Booking 오퍼레이션 하나가 Booking/Cancel/Passenger/Pricing/Retrieve 등 여러 서비스로 잘게 나뉘기 때문. 컨트롤러는 얇고(검증·매핑만), 세션 오케스트레이션은 서비스에 있다. (amadeus-operations에서 상세)

4-2. application 서비스 목록 (11개)

서비스매핑 오퍼레이션핵심 책임
AmadeusFlightSearchServiceSearchFareMasterPricerTravelBoardSearch 검색, 결과 Redis 캐시
AmadeusBookingServiceBookingstateful{} 예약 흐름 오케스트레이션, EOT, Slack 경보, 실패 시 보상 취소
AmadeusPassengerServiceBookingAPIS(여권/탑승객 정보) 변경
AmadeusPricingServiceBooking/TicketingFare_PricePNRWithBookingClass 운임 재계산
AmadeusRetrieveServiceBookingPNR 조회·티켓 문서·발권일 히스토리 보강
AmadeusCancelServiceBooking취소/예상취소/취소가능 판정 + 비동기 보상 취소(pnrCancelAsync)
AmadeusTicketingServiceTicketingready/issue(발권), TST·결제 결합
AmadeusRefundService(환불, Cancel 계열)DocRefund_* 환불 init/update/process
AmadeusFareRuleServiceFareRuleTOPAS ART(ArtClient) 운임 규정 조회
AmadeusQueueServiceQueue큐 카운트/목록/이동/제거
AmadeusCashReceiptServiceCashReceiptTOPAS GPS(GpsClient) 현금영수증 발급/취소

지원 오퍼레이션 매트릭스: Search · Booking · Ticketing · FareRule · Queue · CashReceipt (6종 전부 구현).


5. 중요도 별점

★★★ (매우 높음)

근거 1 — 규모: 11개 공급사 중 최대(약 3.9MB / 873 .kt). 어댑터 코드량의 가장 큰 비중. 근거 2 — 유일한 stateful 세션 패턴: StatefulBuilder + TransactionStatusCode(Start/InSeries/End) + awsse:Session 토큰 릴레이는 이 모듈(과 동일 GDS 계열 sabre/galileo)에만 존재. 세션·예외·signout이 얽혀 디버깅 난도가 가장 높다. 근거 3 — 기능 폭: 6개 오퍼레이션 + 3종 프로토콜(GDS SOAP / ART REST / GPS 오프라인 SOAP)을 한 모듈에서 모두 다룬다. 근거 4 — 결제·환불·세션 누수 등 운영 리스크가 집중: 카드 키인 승인(GpsClient.approve), WS-Security PasswordDigest, EOT 경고 처리 등 실수 시 영향이 큰 영역. → amadeus-pitfalls

온보딩 시 GDS 계열(amadeus/sabre/galileo)을 먼저 이해해야 LCC 모듈이 쉬워지므로, amadeus를 가장 먼저 깊게 학습할 것을 권장.


다음에 읽을 노트