Sabre — 개요

module-sabre arch-supplier-module api-internals protocol-soap

이 노트 한 장 요약

Sabre는 세계 2위 GDS(1S) 와 연동하는 공급사 모듈이다. air-intl-adapter 안에서 파일 수 1위(882개), 코드 라인 2위(약 25,194 LOC) 의 대형 모듈이며, 다른 공급사와 달리 두 가지 프로토콜을 동시에 쓴다 — 검색은 신형 REST(BargainFinderMax v3), 예약/발권/큐/현금영수증은 구형 SOAP(stateful 세션). 비동기는 검색의 병렬 fan-out과 fire-and-forget 후처리에 코루틴을 쓰며, 가장 특이한 코루틴 사용처는 APIS 변경 파이프라인을 runBlocking으로 묶는 SabrePassengerService이다.

관련 노트: 오퍼레이션 상세 · 세션) · 지뢰·주의점 · 전체 아키텍처 · 요청 흐름 기존 API 분석: sabre-gds


1. 공급사 특징 — Sabre는 무엇인가

비즈니스/시장 맥락

항목내용
정체성GDS(Global Distribution System) — 항공사가 아니라 여러 항공사 좌석/운임을 모아 파는 유통망
IATA 사업자 코드1S (Amadeus=1A, Galileo=1G와 같은 GDS 코드 체계)
분류FSC도 LCC도 아닌 GDS (한 모듈로 수많은 항공사를 커버)
주 연동 프로토콜SOAP (Sabre Web Services) + REST(BargainFinderMax/EnhancedAirTicket v3)
인증EPR/password 기반. SOAP는 세션 토큰(wsse:Security), REST는 OAuth Bearer 토큰

왜 GDS를 LCC/FSC와 구분해서 봐야 하나

LCC 모듈(tway, jinair, jejuair)은 항공사 한 곳의 REST API를 호출한다. GDS인 Sabre는 하나의 연동으로 수백 개 항공사의 운임을 검색·예약한다. 그래서 응답 스키마가 “범용”이고 거대하며(파일 882개), 항공사별 예외 처리·운임 규칙 해석이 훨씬 복잡하다. NDC 모듈(koreanair, lufthansa 등)이 항공사 직접연동인 것과도 대비된다.

왜 이렇게(SOAP+REST 혼합) 연동하는가 — 코드로 검증된 사실

Sabre는 레거시 SOAP(OTA_*, EnhancedAirBook, PassengerDetails 등 OpenTravel 기반)와 신형 REST API(BargainFinderMax, BookingManagement, EnhancedAirTicket)를 모두 제공한다. 이 모듈은 오퍼레이션별로 둘을 섞어 쓴다.

flowchart LR
    subgraph REST["REST 경로"]
        SearchOp["검색 Search<br/>BargainFinderMax v3"]
        FareRuleOp["운임규칙 FareRule<br/>BFM Rule"]
    end
    subgraph SOAP["SOAP 경로"]
        RevalOp["운임재검증 revalidate<br/>OTA_AirLowFareSearch<br/>FareRule 단계에서 사용"]
        FareRuleSoap["운임규칙 FareRule<br/>StructureFareRules"]
        BookingOp["예약 조회 취소 분리<br/>stateful 세션"]
        TicketOp["발권 Ticketing"]
        ApisOp["APIS 변경<br/>세션 파이프라인"]
        QueueOp["큐 Queue"]
        PaymentOp["결제 현금영수증<br/>PaymentService WSDL"]
    end
    RestClient["SabreRestClient"]
    SabreClient["SabreClient"]
    PaymentClient["SabrePaymentClient"]

    SearchOp --> RestClient
    FareRuleOp --> RestClient
    RevalOp --> SabreClient
    FareRuleSoap --> SabreClient
    BookingOp --> SabreClient
    TicketOp --> SabreClient
    ApisOp --> SabreClient
    QueueOp --> SabreClient
    PaymentOp --> PaymentClient
  • 검색(Search) → REST (BargainFinderMax v3) ← SabreRestClient
  • 운임재검증(revalidate) → SOAP (OTA_AirLowFareSearch) ← SabreClient (FareRule 단계에서 사용)
  • 운임규칙(FareRule) → REST(BFM Rule) + SOAP(StructureFareRules)
  • 예약/조회/취소/분리 → SOAP (stateful 세션) ← SabreClient
  • 발권(Ticketing) → SOAP ← SabreClient
  • APIS 변경 → SOAP (세션 파이프라인) ← SabreClient
  • 큐(Queue) → SOAP ← SabreClient
  • 결제/현금영수증 → SOAP (PaymentService WSDL) ← SabrePaymentClient

코드 근거:

  • 검색이 REST임 → SabreFlightSearchService.search()sabreRestClient.search(...)를 호출 (application/SabreFlightSearchService.kt:70). 토큰도 sabreRestClient.getToken()으로 받음(:66).
  • 그러나 SOAP search/revalidate도 살아있음SabreClient.search()(infrastructure/soap/SabreClient.kt:141), SabreClient.revalidate()(:236). 그중 revalidate는 FareRule 흐름에서 실제 호출됨(SabreFlightSearchService.kt:185, SabreFareRuleService.kt:42).
  • 예약/발권/큐/현금영수증은 전부 SOAP 클라이언트(sabreClient 또는 sabrePaymentClient)를 주입받음(§4 표 참조).

"SOAP 중심"이라는 표현의 정확한 의미

운영의 핵심 트랜잭션(예약→발권→취소) 은 전부 SOAP 세션 위에서 돈다. REST는 주로 검색(읽기 성격, 무상태) 에 도입됐다. 즉 “검색만 REST로 현대화했고 트랜잭션은 여전히 SOAP”라고 이해하면 정확하다. 자세한 프로토콜 차이는 sabre-protocol 참고.


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

어댑터 전체에서 Sabre의 위치

공급사파일 수LOC(근사)비고
galileo52527,447LOC 1위 (Universal API 스키마)
sabre88225,194파일 1위 / LOC 2위
amadeus87324,324파일 2위
amadeusndc35712,693
tway25310,843
(이하 생략)

신입에게

Sabre는 amadeus와 함께 어댑터에서 가장 무거운 두 모듈이다. 882개 파일 중 803개(91%)가 infrastructure — 대부분 SOAP/REST 요청·응답 DTO(JAXB/Jackson 매핑용)다. 즉 “비즈니스 로직 코드”는 의외로 적고(application 9개, 약 1,892 LOC), 대부분이 외부 스키마를 그대로 옮긴 DTO다. 코드를 읽을 때 DTO 800개에 압도되지 말고 application(서비스)과 infrastructure/soap/SabreClient.kt(1,109 라인, 27개 메서드)부터 보면 된다.

서브패키지별 규모 (실측)

서브패키지파일 수LOC(근사)역할
interfaces6388REST 컨트롤러 6개(Triple 내부 API 진입점)
application91,892비즈니스 서비스(오케스트레이션)
infrastructure80321,224외부 Sabre API 클라이언트 + 요청/응답 DTO
domain2367FareItinerary 도메인 모델 + Redis 리포지토리
support611,293enum 38개, model 18개, util 4개, Constants
configuration130Sabre 전용 Redis 설정

디렉터리 트리 (실제)

supplier/sabre
├── interfaces/controller/internals/      # 공개 진입점 (컨트롤러 6개)
├── application/                          # 서비스 9개
├── infrastructure/
│   ├── soap/                             # 361 파일 — SOAP 경로
│   │   ├── SabreClient.kt                # ★ 핵심 SOAP 클라이언트 (1,109 라인)
│   │   ├── SabrePaymentClient.kt         # 결제/현금영수증 SOAP(PaymentService WSDL)
│   │   ├── PaymentError.kt
│   │   ├── request/  (25개 오퍼레이션 디렉터리: otaairlowfaresearch, enhancedairbook,
│   │   │              airticket, getreservation, passengerdetails, queueaccess,
│   │   │              sessioncreate/close, endtransaction, divideitinerary, voidticket ...)
│   │   └── response/ (25개 디렉터리 + common/)
│   └── rest/                             # 442 파일 — REST 경로
│       ├── SabreRestClient.kt            # ★ REST 클라이언트 (검색/예약관리/발권/취소)
│       ├── SabreFareRuleClient.kt        # BFM 운임규칙
│       ├── request/  (bargainfindermax, bookingmanagement,
│       │              createpassengernamerecord, enhancedairticket)
│       └── response/ (위 4개 + token/, ApplicationResult.kt)
├── domain/
│   ├── model/SabreFlightSearch.kt
│   └── repository/SabreFareItineraryRepository.kt   # Redis 캐시
├── support/
│   ├── enums/   (38개: BookingStatusType, TicketStatus, FormOfPaymentType,
│   │             SabreSoapHeaderNamespace, QueueAccessActionType ...)
│   ├── model/   (18개: Booking, Passenger, Ticket, Fare, Payment, Refund ...)
│   ├── util/    (AirportUtils, FareComponentUtils, FormatUtils, PaymentUtils)
│   └── Constants.kt
└── configuration/RedisConfiguration.kt   # SabreRedisConfiguration (FareItinerary용 Redis 템플릿)

soap(361)보다 rest(442) 파일이 더 많은 이유

REST(BargainFinderMax) 응답 JSON 스키마가 SOAP보다 더 잘게 쪼개진 DTO 구조라서다. 파일 수가 곧 중요도는 아니다 — 트랜잭션 무게중심은 여전히 soap/SabreClient.kt 한 파일에 있다.


3. 핵심 파일 표

파일 (상대경로)역할주목 포인트
infrastructure/soap/SabreClient.ktSOAP 핵심 클라이언트. 세션·예약·발권·큐·운임규칙 등 27개 메서드1,109 라인. getSessionToken/closeSessionToken(:315/:343), endTransaction(:610) — stateful 세션의 심장
infrastructure/rest/SabreRestClient.ktREST 클라이언트. 검색·예약관리·발권·환불/v3/auth/token(:83) OAuth, search(:100), book(:267), refundTickets(:411). Redis에 토큰 캐시(:486)
infrastructure/soap/SabrePaymentClient.kt결제/현금영수증 SOAPPaymentService?wsdl, TossPayService, PG 카드코드 조회(pgCardCode :209)
infrastructure/rest/SabreFareRuleClient.ktBFM 운임규칙 조회응답을 gzip 압축 저장(compress :124)
application/SabreFlightSearchService.kt검색 오케스트레이션REST 검색 + SOAP revalidate 혼합, 병렬 pmap/withBlocking(:67)
application/SabreBookingService.kt예약 생성/조회/확정/분리/repricingfire-and-forget 후처리 withLaunch(:148,162,168)
application/SabreTicketingService.kt발권 ready/issue
application/SabreCancelService.kt취소(void/refund)SOAP+REST 모두 주입, Slack 미완료 void 경보(:150)
application/SabrePassengerService.ktAPIS(여권/연락처) 변경runBlocking으로 세션 파이프라인 묶음 — §5 참조
application/SabrePaymentService.kt결제 승인/취소Slack 결제취소실패 경보(:66)
application/SabreQueueService.kt큐 PNR 조회/제거@Retryable(:85), Slack 큐 실패 경보
application/SabreCashReceiptService.kt현금영수증 발급/취소
application/SabreFareRuleService.kt운임규칙 + 구조화 운임규칙revalidate 재사용
domain/repository/SabreFareItineraryRepository.kt검색결과 Redis 캐시
configuration/RedisConfiguration.ktSabreRedisConfigurationFareItinerary Redis 템플릿gzip 직렬화(GzipRedisSerializer)
support/enums/SabreSoapHeaderNamespace.ktSOAP 헤더 네임스페이스eb(ebXML messageHeader), wsse(WS-Security) — 세션 토큰이 wsse에 실림

4. 공개 인터페이스 (컨트롤러 & 서비스)

4.1 중앙 디스패처는 없다

이 어댑터에는 중앙 라우터가 없고, 공급사마다 자체 컨트롤러@RequestMapping("/internals/SABRE/...")로 Triple 예약 시스템의 내부 API를 직접 노출한다. Sabre는 컨트롤러 6개로 6개 오퍼레이션을 커버한다. 전체 라우팅 규칙은 request-flow 참고.

4.2 컨트롤러 → 엔드포인트 매핑 (실측)

컨트롤러베이스 경로엔드포인트오퍼레이션
SabreSearchController/internals/SABRE/searchPOST 검색 / GET 상세(detail)Search
SabreBookingController/internals/SABRE/bookingsPOST 생성 · PUT /{pnr} APIS변경 · PUT /{pnr}/cancel 취소 · GET /{pnr}/expected-cancel · GET /{pnr}/cancelable · GET /{pnr} 조회 · GET /{pnr}/check-pnr · GET /{pnr}/confirm · GET /{pnr}/repricing · POST /{pnr}/divideBooking
SabreTicketingController/internals/SABRE/ticketingPOST /ready · POST 발권(issue)Ticketing
SabreFareRuleController/internals/SABRE/fare-rulesGET 운임규칙 · GET /structured 구조화 운임규칙FareRule
SabreQueueController/internals/SABRE/queuesGET 큐 PNR 목록 · PUT 큐에서 제거Queue
SabreCashReceiptController/internals/SABRE/cash-receiptsPOST /issue 발급 · PUT /cancel 취소CashReceipt

검색 컨트롤러에만 서킷브레이커가 붙어 있다

@CircuitBreaker(name = "sabreSearch", fallbackMethod = "searchFallback")
@PostMapping
fun search(@RequestBody request: SearchRequest): ResponseEntity<List<FareItineraryView>> { ... }

(SabreSearchController.kt:24) 검색은 트래픽이 많고 실패해도 빈 목록(emptyList())으로 graceful degradation이 가능하기 때문이다. fallback에서 Datadog 스팬에 supplier.circuit-breaker=OPEN 태그를 찍는다(:67-73). 예약/발권은 서킷을 열면 안 되므로(돈·재고 정합성) 컨트롤러에 @CircuitBreaker가 없다. 이 “이벤트=상태전이+경보” 모델은 resilience-and-events 참고.

4.3 application 서비스 9개

서비스주입 클라이언트담당 오퍼레이션
SabreFlightSearchServiceSOAP + RESTSearch (+ revalidate)
SabreBookingServiceSOAPBooking(생성/조회/확정/분리/repricing)
SabreCancelServiceSOAP + RESTBooking(취소/void/refund)
SabrePassengerServiceSOAPBooking(APIS 변경)
SabreTicketingServiceSOAPTicketing
SabreFareRuleServiceREST(FareRule) + SOAPFareRule
SabreQueueServiceSOAPQueue
SabreCashReceiptServiceSOAP(Payment)CashReceipt
SabrePaymentServiceSOAP(Payment)(결제 — Ticketing/Cancel에서 사용)

컨트롤러 6 ≠ 서비스 9

Booking 컨트롤러 하나가 4개 서비스(SabreBookingService/SabrePassengerService/SabreCancelService + 내부적으로 SabrePaymentService)로 분해된다. 책임이 큰 오퍼레이션을 서비스로 쪼갠 결과다. 각 메서드의 시퀀스는 sabre-operations에서 다룬다.


5. SabrePassengerService — 코루틴 사용처 (자세히)

이 모듈에서 가장 독특한 코루틴 사용

어댑터 전체에서 코루틴은 support/util/CoroutineExtensions.ktSabrePassengerService에 집중된다. Sabre 안에서는 두 가지 패턴이 명확히 갈린다.

패턴 A — runBlocking으로 stateful 세션 파이프라인 묶기 (SabrePassengerService 전용)

SabrePassengerService.changeApis()는 하나의 SOAP 세션 토큰 안에서 조회→삭제→재생성→커밋→재조회를 순서대로 실행해야 한다. 이 일련의 작업을 runBlocking { ... }으로 감싸고, finally에서 반드시 세션을 닫는다.

fun changeApis(pnr: String, validatingCarrier: String, passengers: List<Passenger>): List<Passenger> {
    val token = sabreClient.getSessionToken()
    try {
        return runBlocking {
            val booking = sabreClient.getBooking(token, pnr)
            // ... 기존 SSR/OSI id 수집 ...
            consecutiveNumbersToRange(legacySsrIds)?.forEach { sabreClient.deleteSsr(token, it) }
            consecutiveNumbersToRange(legacyOsiIds)?.forEach { sabreClient.deleteOsi(token, it) }
            sabreClient.createApis(token, validatingCarrier, changePassengers)
            sabreClient.endTransaction(token)              // ← 세션 커밋
            sabreClient.getBooking(token, pnr).passengers
        }
    } finally {
        sabreClient.closeSessionToken(token)               // ← 세션은 무조건 닫는다
    }
}

(application/SabrePassengerService.kt:12-71)

삭제 순서 함정 — consecutiveNumbersToRange

SSR/OSI를 삭제하면 id가 앞으로 밀린다. 그래서 작은 id부터 지우면 잘못된 항목이 지워진다. 이 함수는 [1,2,3,5,7,8,9,10][7-10,5,1-3]처럼 큰 번호부터 역순 범위로 변환해 안전하게 삭제한다(:73-92). 이 손맛 나는 로직이 깨지면 엉뚱한 승객 정보가 삭제된다. sabre-pitfalls 참조.

패턴 B — CoroutineScope(Dispatchers.IO).withLaunch { } fire-and-forget 후처리 (나머지 서비스 다수)

검색 키 제거, 미노출 운임 저장, 비동기 PNR 취소 등 결과를 기다릴 필요 없는 후처리에 쓰인다.

private fun cancelAsync(pnr: String) {
    CoroutineScope(Dispatchers.IO).withLaunch { cancelService.onlyPnrCancel(pnr) }
}

(application/SabreBookingService.kt:148) — withLaunchsupport/util/CoroutineExtensions.kt에 정의되며, 코루틴 예외는 support/exception/AdapterCoroutineExceptionHandler.kt가 처리한다. 검색의 fan-out 병렬화는 withBlocking(Dispatchers.IO) { ... .pmap { } }(SabreFlightSearchService.kt:67). 비동기 전반은 async-coroutines 참고.


6. 중요도 별점

★★★ (최상)

Sabre는 반드시 깊게 봐야 하는 모듈이다

근거:

  1. 규모 — 파일 1위(882), LOC 2위(25,194). 어댑터의 양대 GDS 중 하나.
  2. 유일한 SOAP+REST 하이브리드 — 검색=REST, 트랜잭션=SOAP. 다른 모듈에 없는 마이그레이션 중간 상태를 보여준다.
  3. stateful 세션 모델의 교과서getSessionToken→...→endTransaction→closeSessionToken 패턴. amadeus(TOPAS PNR 세션)와 함께 “GDS 세션 정합성”을 배우는 핵심. 세션 누수/미커밋이 곧 장애.
  4. 코루틴 정석 사례runBlocking 세션 파이프라인(SabrePassengerService) + withLaunch fire-and-forget. 어댑터 비동기 학습의 레퍼런스.
  5. 다채로운 오퍼레이션 — 6개 컨트롤러로 Search/Booking/Ticketing/FareRule/Queue/CashReceipt 전부 커버. 결제(PaymentService WSDL)·PG 카드코드·TossPay까지 닿아 있어 결제 흐름까지 배울 수 있다.

학습 동선

① 이 노트 → ② 프로토콜(SOAP 세션 vs REST 토큰 차이부터) → ③ 오퍼레이션(예약→발권→취소 시퀀스) → ④ 지뢰(세션 누수·삭제 순서·서킷브레이커 fallback). 전체 맥락은 onboarding-map·system-architecture에서 잡는다.