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-110—soapBodyDeserializerOf가soap(content).soapBody(mapper)로 SOAP body를 역직렬화하고, 동시에 SOAP header의awsse: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 SOAP | infrastructure/AmadeusClient.kt | SOAP/XML (stateful) | Search·Booking·Ticketing·Queue·Refund 등 핵심 전부 |
| ART | infrastructure/topas/ArtClient.kt | JSON REST (x-api-key) | FareRule 운임 규정 조회 (/api/v1/art/getrule/...) |
| GPS | infrastructure/topas/GpsClient.kt | offline 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.kt—Session?을 들고 다니는 빌더.start{}→TransactionStatusCode.Start,inSeries{}→InSeries(sequenceNumber+1),end{}→End로 세션 상태를 전이시킨다.infrastructure/Session.kt— SOAPawsse:Session헤더에 매핑되는sessionId,sequenceNumber,securityToken.support/enums/TransactionStatusCode.kt—Start / InSeries / End3값.
// 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/retrieve는catch (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/ | 6 | Triple 내부 API로 노출되는 6개 REST 컨트롤러 (controller/internals/) |
application/ | 11 | 오케스트레이션 서비스(세션 흐름·재시도·Slack 경보 조립) |
infrastructure/ | 794 | SOAP/REST 클라이언트 + 오퍼레이션별 request/response DTO(대부분 스키마 매핑 클래스) |
support/ | 58 | enums/(40+), model/(도메인 표현), util/(StatefulBuilder 등) |
domain/ | 3 | model/FareItinerary, repository/(Redis 캐시 repo 2종) |
configuration/ | 1 | AmadeusRedisConfiguration(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.kt | SOAP awsse:Session 헤더 ↔ Kotlin 데이터 클래스 | sessionId/sequenceNumber/securityToken |
support/util/StatefulBuilder.kt | 세션 상태 전이기. stateful{}/start{}/inSeries{}/end{} 확장함수 | 이 모듈을 이해하는 출발점 |
infrastructure/AmadeusResponse.kt | AmadeusResponse<T>(session, body) 래퍼 | 모든 응답이 세션+body 쌍 |
infrastructure/AmadeusKeyInError.kt | 카드 키인 결제 에러코드 → 사용자 메시지 매핑 | approveByKeyIn에서 사용 |
infrastructure/topas/ArtClient.kt | TOPAS ART 운임규정 REST 클라이언트 | JSON, x-api-key, @Retryable(maxAttempts=2) |
infrastructure/topas/GpsClient.kt | TOPAS GPS 오프라인 SOAP (카드 승인/취소·현금영수증) | stateless, /GPS_Approval_RequestService |
support/enums/AmadeusSoapHeaderNamespace.kt | WS-Addressing·WS-Security·Session 네임스페이스 정의 | SOAP 헤더 빌드의 상수 출처 |
domain/model/AmadeusFlightSearch.kt + domain/repository/AmadeusFareItineraryRepository.kt | 검색 결과(FareItinerary) Redis 캐시 | 검색→상세→예약 간 키 전달 |
configuration/RedisConfiguration.kt | AmadeusRedisConfiguration — 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/search | POST / (검색, @CircuitBreaker("amadeusSearch"))GET / (상세 detail) | AmadeusFlightSearchService, FlightAmenityService |
AmadeusBookingController | /internals/AMADEUS/bookings | POST / (create)PUT /{pnr} (changeApis)PUT /{pnr}/cancelGET /{pnr}/expected-cancelGET /{pnr}/cancelableGET /{pnr} (retrieve)GET /{pnr}/check-pnrGET /{pnr}/confirmGET /{pnr}/repricingPOST /{pnr}/divide | AmadeusBookingService, AmadeusPassengerService, AmadeusCancelService |
AmadeusTicketingController | /internals/AMADEUS/ticketing | POST /readyPOST / (issue 발권) | AmadeusTicketingService |
AmadeusFareRuleController | /internals/AMADEUS/fare-rules | GET / (운임규정)GET /structured (구조화 규정) | AmadeusFareRuleService, AmadeusFlightSearchService |
AmadeusQueueController | /internals/AMADEUS/queues | GET / (큐 PNR 목록)PUT / (remove) | AmadeusQueueService |
AmadeusCashReceiptController | /internals/AMADEUS/cash-receipts | POST /issuePUT /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개)
| 서비스 | 매핑 오퍼레이션 | 핵심 책임 |
|---|---|---|
AmadeusFlightSearchService | Search | FareMasterPricerTravelBoardSearch 검색, 결과 Redis 캐시 |
AmadeusBookingService | Booking | stateful{} 예약 흐름 오케스트레이션, EOT, Slack 경보, 실패 시 보상 취소 |
AmadeusPassengerService | Booking | APIS(여권/탑승객 정보) 변경 |
AmadeusPricingService | Booking/Ticketing | Fare_PricePNRWithBookingClass 운임 재계산 |
AmadeusRetrieveService | Booking | PNR 조회·티켓 문서·발권일 히스토리 보강 |
AmadeusCancelService | Booking | 취소/예상취소/취소가능 판정 + 비동기 보상 취소(pnrCancelAsync) |
AmadeusTicketingService | Ticketing | ready/issue(발권), TST·결제 결합 |
AmadeusRefundService | (환불, Cancel 계열) | DocRefund_* 환불 init/update/process |
AmadeusFareRuleService | FareRule | TOPAS ART(ArtClient) 운임 규정 조회 |
AmadeusQueueService | Queue | 큐 카운트/목록/이동/제거 |
AmadeusCashReceiptService | CashReceipt | TOPAS 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를 가장 먼저 깊게 학습할 것을 권장.
다음에 읽을 노트
- 오퍼레이션 상세 — 6개 오퍼레이션이 어떤 SOAP 메시지 시퀀스로 구현되는지
- 프로토콜·세션 메커니즘 — SOAP 헤더, WS-Security, ART/GPS 3종 채널
- 지뢰·주의점 — 세션 누수, EOT 경고, 보상 취소, 키인 결제
- 상위 맥락: 시스템 아키텍처 · 요청 흐름
- 심화 비교 자료: amadeus-gds