FareRuleProxyController DFS (기능명세서)

1. API 개요 및 목적

1.1 개요

항목
클래스명FareRuleProxyController
패키지com.triple.air.intl.search.interfaces.controller.internal.proxy
소스 위치air-intl-search/src/main/kotlin/.../interfaces/controller/internal/proxy/FareRuleProxyController.kt:16
Base Path/internals/proxy/flights/fare-rules
용도내부 프록시 서비스용 운임 규정 조회 API

1.2 목적

FareRuleProxyController는 **내부 서비스 간 통신(Internal Proxy)**을 위한 운임 규정 조회 API를 제공합니다. 주요 특징:

  • 내부 시스템 간 호출을 위한 전용 엔드포인트
  • MIX 항공권(분리 발권) 지원을 위한 다중 detailKey 처리
  • 동기 방식의 직접 응답 (폴링 불필요)
  • 판매 채널 및 퍼널 헤더 필수

2. 필수 헤더 요구사항

컨트롤러 레벨에서 다음 헤더가 필수로 요구됩니다:

헤더명상수설명
x-triple-sales-channelConstants.TRIPLE_SALES_CHANNEL_HEADER판매 채널 식별자
x-triple-sales-funnelConstants.TRIPLE_SALES_FUNNEL_HEADER판매 퍼널 식별자

소스 위치: FareRuleProxyController.kt:14, Constants.kt:6-7

@RequestMapping(
    "/internals/proxy/flights/fare-rules",
    headers = [Constants.TRIPLE_SALES_CHANNEL_HEADER, Constants.TRIPLE_SALES_FUNNEL_HEADER]
)

헤더가 누락된 경우 Spring Framework에서 404 또는 헤더 관련 오류를 반환합니다.


3. 엔드포인트별 상세 분석

3.1 운임 규정 그룹 조회 (getFareRulesGroup)

기본 정보

항목
엔드포인트GET /internals/proxy/flights/fare-rules/{detailKey1}
대체 엔드포인트GET /internals/proxy/flights/fare-rules/{detailKey1}/{detailKey2}
메서드getFareRulesGroup
소스 위치FareRuleProxyController.kt:20-41

Request 스펙

Path Variables

파라미터타입필수설명
detailKey1StringO첫 번째 항공권 상세 키
detailKey2String?X두 번째 항공권 상세 키 (MIX 항공권용)

Query Parameters

파라미터타입필수기본값설명
adultIntO-성인 승객 수
childIntX0아동 승객 수
infantIntX0유아 승객 수

Response 스펙

HTTP Status: 200 OK

Response Type: List<InternalProxyFareRuleGroupView>

// 소스: FareRulesView.kt:57-72
data class InternalProxyFareRuleGroupView(
    val detailKey: String,
    val fareRules: List<InternalProxyFareRulesView>,
)
 
// 소스: FareRulesView.kt:44-55
data class InternalProxyFareRulesView(
    val groupTitle: String,
    val rules: List<InternalProxyFareRuleItemView>,
)
 
// 소스: FareRulesView.kt:74-89
data class InternalProxyFareRuleItemView(
    val type: FareRuleType?,    // REFUND_AND_CHANGE, BAGGAGE, MILEAGE, COMMON
    val title: String,
    val content: String?,
    val order: Int,
)

FareRuleType Enum (소스: FareRuleType.kt:3-8)

설명
REFUND_AND_CHANGE환불 및 변경 규정
BAGGAGE수하물 규정
MILEAGE마일리지 규정
COMMON일반 규정

3.2 구조화된 운임 규정 조회 (getStructuredFareRules)

기본 정보

항목
엔드포인트GET /internals/proxy/flights/fare-rules/structured/{detailKey1}
대체 엔드포인트GET /internals/proxy/flights/fare-rules/structured/{detailKey1}/{detailKey2}
메서드getStructuredFareRules
소스 위치FareRuleProxyController.kt:43-56

Request 스펙

Path Variables

파라미터타입필수설명
detailKey1StringO첫 번째 항공권 상세 키
detailKey2String?X두 번째 항공권 상세 키 (MIX 항공권용)

참고: 구조화된 운임 규정 조회에서는 승객 수 파라미터가 필요하지 않습니다.

Response 스펙

HTTP Status: 200 OK

Response Type: List<InternalProxyStructuredFareRuleView>

// 소스: FareRulesView.kt:91-103
data class InternalProxyStructuredFareRuleView(
    val detailKey: String,
    val fareRules: InternalProxyStructuredFareRuleResponseView,
)
 
// 소스: FareRulesView.kt:105-114
data class InternalProxyStructuredFareRuleResponseView(
    val segments: List<InternalProxySegmentFareRulePolicyView>,
)
 
// 소스: FareRulesView.kt:116-127
data class InternalProxySegmentFareRulePolicyView(
    val id: String,
    val policy: InternalProxyFareRulePolicyView,
)

InternalProxyFareRulePolicyView 구조 (소스: FareRulesView.kt:129-146)

data class InternalProxyFareRulePolicyView(
    val cancellation: InternalProxyCancellationPolicyView?,
    val change: InternalProxyChangePolicyView?,
    val baggage: InternalProxyBaggagePolicyView?,
    val mileage: InternalProxyMileagePolicyView?,
    val additionalServices: InternalProxyAdditionalServicesPolicyView?,
)

세부 정책 구조

정책클래스주요 필드
취소 정책InternalProxyCancellationPolicyViewavailable, conditions
변경 정책InternalProxyChangePolicyViewavailable, conditions
수하물 정책InternalProxyBaggagePolicyViewtype, baggages
마일리지 정책InternalProxyMileagePolicyViewavailable, amount, rate
부가 서비스InternalProxyAdditionalServicesPolicyViewseatSelectionAvailable, mealIncluded

PenaltyCondition 구조 (소스: FareRulesView.kt:174-189)

data class InternalProxyPenaltyConditionView(
    val type: String,       // 조건 유형
    val period: Int,        // 기간 (일/시간)
    val currency: String,   // 통화 코드
    val fee: BigDecimal,    // 수수료
)

4. 비즈니스 로직 흐름

4.1 운임 규정 그룹 조회 플로우

sequenceDiagram
    participant Client
    participant Controller as FareRuleProxyController
    participant Validator as checkSearchablePassengers
    participant Service as FareRuleService
    participant FlightSearch as FlightSearchService
    participant Adapter as AdapterClient
    participant External as External API

    Client->>Controller: GET /fare-rules/{detailKey1}/{detailKey2}
    Controller->>Validator: checkSearchablePassengers(adult, child, infant)

    alt 유효성 검사 실패
        Validator-->>Client: MethodArgumentInvalidException
    end

    Controller->>Service: getFareRulesGroups(detailKeys, adult, child, infant)
    Service->>Service: validateMultiTicketParameter(detailKeys)

    alt detailKeys.size > 1
        Service->>FlightSearch: getFlightItem(detailKey)
        FlightSearch-->>Service: FlightItem

        alt !flightItem.isMix
            Service-->>Client: MethodArgumentInvalidException(INVALID_PARAMETER)
        end
    end

    loop 각 detailKey에 대해 (병렬)
        Service->>Adapter: getFareRulesGroups(detailKey, adult, child, infant)
        Adapter->>External: GET /{supplier}/fare-rules
        External-->>Adapter: FareRuleResponse[]
        Adapter-->>Service: FareRuleGroup
    end

    Service-->>Controller: List<FareRuleGroup>
    Controller-->>Client: List<InternalProxyFareRuleGroupView>

4.2 구조화된 운임 규정 조회 플로우

sequenceDiagram
    participant Client
    participant Controller as FareRuleProxyController
    participant Service as FareRuleService
    participant Adapter as AdapterClient
    participant External as External API

    Client->>Controller: GET /fare-rules/structured/{detailKey1}/{detailKey2}
    Controller->>Service: getStructuredFareRules(detailKey1, detailKey2)

    loop 각 detailKey에 대해 (병렬)
        Service->>Adapter: getStructuredFareRules(detailKey)
        Adapter->>External: GET /{supplier}/fare-rules/structured
        External-->>Adapter: StructuredFareRuleResponse
        Adapter-->>Service: StructuredFareRuleDetail
    end

    Service->>Service: StructuredFareRule.of(detailKey, fareRules)
    Service-->>Controller: List<StructuredFareRule>
    Controller-->>Client: List<InternalProxyStructuredFareRuleView>

5. Service 연동 분석

5.1 FareRuleService

소스 위치: FareRuleService.kt

getFareRulesGroups (라인 37-59)

fun getFareRulesGroups(
    detailKeys: List<String>,
    adult: Int,
    child: Int,
    infant: Int,
): List<FareRuleGroup> {
    validateMultiTicketParameter(detailKeys)  // MIX 항공권 검증
 
    return withBlocking(Dispatchers.IO) {
        detailKeys.pmap { detailKey ->        // 병렬 처리
            adapterClient.getFareRulesGroups(...)
        }
            .getOrThrow()
            .sortedBy { fareRuleGroup ->
                detailKeys.indexOf(fareRuleGroup.detailKey)  // 입력 순서 유지
            }
    }
}

getStructuredFareRules (라인 61-74)

fun getStructuredFareRules(detailKey1: String, detailKey2: String?): List<StructuredFareRule> {
    val detailKeys = listOfNotNull(detailKey1, detailKey2)
 
    return withBlocking(Dispatchers.IO) {
        detailKeys.pmap { detailKey ->
            detailKey to adapterClient.getStructuredFareRules(detailKey)
        }.getOrThrow()
            .map { (detailKey, structuredFareRules) ->
                StructuredFareRule.of(detailKey, structuredFareRules)
            }
    }
}

5.2 AdapterClient 연동

소스 위치: AdapterClient.kt

getFareRulesGroups (라인 156-195)

  • 엔드포인트: {endpoint}/{supplier}/fare-rules
  • HTTP Method: GET
  • 쿼리 파라미터: key, adult, child, infant
  • detailKey 분해: destructDetailKey() 함수로 groupKey, key, supplier 추출

getStructuredFareRules (라인 197-220)

  • 엔드포인트: {endpoint}/{supplier}/fare-rules/structured
  • HTTP Method: GET
  • 쿼리 파라미터: key
  • 승객 수 불필요: 구조화된 규정은 승객 수와 무관

6. detailKey1/detailKey2 처리 (MIX 항공권 지원)

6.1 MIX 항공권이란?

MIX 항공권은 분리 발권 상품으로, 가는 편과 오는 편이 서로 다른 항공권으로 발권되는 경우입니다.

구분일반 왕복MIX 항공권
발권 수1장2장
detailKey1개2개
운임 규정1세트2세트 (각 항공권별)
isMix 플래그falsetrue

6.2 URL 패턴

# 단일 항공권 (일반)
GET /internals/proxy/flights/fare-rules/{detailKey1}

# MIX 항공권 (분리 발권)
GET /internals/proxy/flights/fare-rules/{detailKey1}/{detailKey2}

6.3 처리 로직 (소스: FareRuleProxyController.kt:29, FareRuleService.kt:115-126)

// Controller에서 detailKeys 리스트 생성
val detailKeys = listOfNotNull(detailKey1, detailKey2)
 
// Service에서 MIX 항공권 검증
private fun validateMultiTicketParameter(detailKeys: List<String>) {
    if (detailKeys.size > 1) {
        withBlocking(Dispatchers.IO) {
            detailKeys.pmap {
                val flightItem = flightSearchService.getFlightItem(it)
                if (!flightItem.isMix) {
                    throw MethodArgumentInvalidException(MessageKey.INVALID_PARAMETER)
                }
            }.getOrThrow()
        }
    }
}

6.4 검증 규칙

조건결과
detailKey2 = null검증 스킵, 단일 항공권으로 처리
detailKeys.size > 1 && 모두 isMix = true정상 처리
detailKeys.size > 1 && 하나라도 isMix = falseMethodArgumentInvalidException

7. FareRuleController와의 차이점

7.1 비교 표

항목FareRuleControllerFareRuleProxyController
경로/fare-rules/internals/proxy/flights/fare-rules
용도외부 클라이언트용내부 서비스 간 통신
헤더 요구없음x-triple-sales-channel, x-triple-sales-funnel 필수
응답 방식폴링 기반 (비동기)직접 응답 (동기)
MIX 항공권미지원 (단일 detailKey)지원 (detailKey1/detailKey2)
구조화된 규정미지원지원 (/structured 엔드포인트)
응답 타입FareRuleInfoViewInternalProxyFareRuleGroupView

7.2 FareRuleController 응답 방식 (폴링)

sequenceDiagram
    participant Client
    participant Controller as FareRuleController
    participant Service as FareRuleService
    participant Cache as FareRuleRepository

    Client->>Controller: GET /fare-rules?flightDetailKey=...
    Controller->>Service: init(detailKey, ...)
    Service->>Cache: save(PENDING)
    Service-->>Controller: UUID (fareRuleKey)
    Controller-->>Client: 202 Accepted + {"key": UUID}

    Note over Service: 백그라운드에서 운임 규정 조회

    Client->>Controller: GET /fare-rules/{fareRuleKey}
    Controller->>Service: getFareRules(fareRuleKey)
    Service->>Cache: find(fareRuleKey)

    alt status = PENDING
        Cache-->>Client: 202 Accepted + {status: PENDING}
    else status = COMPLETE
        Cache-->>Client: 200 OK + {status: COMPLETE, fareRules: [...]}
    else status = ERROR
        Cache-->>Client: Exception
    end

7.3 FareRuleProxyController 응답 방식 (직접)

sequenceDiagram
    participant Client
    participant Controller as FareRuleProxyController
    participant Service as FareRuleService
    participant Adapter as AdapterClient

    Client->>Controller: GET /internals/proxy/flights/fare-rules/{detailKey}
    Controller->>Service: getFareRulesGroups(...)
    Service->>Adapter: 외부 API 호출 (동기)
    Adapter-->>Service: FareRuleGroup
    Service-->>Controller: List<FareRuleGroup>
    Controller-->>Client: 200 OK + List<InternalProxyFareRuleGroupView>

7.4 주요 차이점 요약

  1. 폴링 vs 직접 응답

    • FareRuleController: 키 발급 후 폴링 필요
    • FareRuleProxyController: 즉시 결과 반환
  2. MIX 항공권 지원

    • FareRuleController: 단일 flightDetailKey만 지원
    • FareRuleProxyController: detailKey1, detailKey2 지원
  3. 유지보수 시간 체크

    • FareRuleController: checkMaintenanceTime(supplier) 호출
    • FareRuleProxyController: 유지보수 시간 체크 없음

8. 에러 처리 및 예외 상황

8.1 승객 수 유효성 검사

소스: SearchValidator.kt:6-30

조건예외MessageKey
adult < 1MethodArgumentInvalidExceptionINVALID_PASSENGERS
adult + child + infant > 9MethodArgumentInvalidExceptionINVALID_PASSENGERS_COUNT
child > adult * 3MethodArgumentInvalidExceptionINVALID_PASSENGERS_CHILD
adult < infantMethodArgumentInvalidExceptionINVALID_PASSENGERS_INFANT

8.2 MIX 항공권 검증 에러

소스: FareRuleService.kt:115-126

조건예외MessageKey
2개의 detailKey 중 하나라도 isMix = falseMethodArgumentInvalidExceptionINVALID_PARAMETER

8.3 외부 API 에러

소스: AdapterClient.kt:188-194, 213-218

에러 코드예외MessageKey
SOLD_OUTInternationalSearchExceptionSOLD_OUT
기타InternationalSearchExceptionFARE_RULE_FAILED

8.4 캐시 키 관련 에러

조건예외MessageKey
detailKey로 FlightItem 조회 실패CacheKeyInvalidExceptionINVALID_CACHE_KEY

8.5 예외 클래스 계층 구조

소스: Exceptions.kt

ApiException (abstract)
├── MethodArgumentInvalidException  // 파라미터 유효성 검사 실패
├── CacheKeyInvalidException        // 캐시 키 무효
├── InternationalSearchException    // 일반 검색 오류
└── SystemMaintenanceException      // 시스템 점검 중

9. 관련 도메인 모델 분석

9.1 FareRuleGroup

소스: FareRuleGroup.kt:3-6

data class FareRuleGroup(
    val detailKey: String,
    val fareRules: List<FareRule>,
)

9.2 FareRule

소스: FareRule.kt:7-21

data class FareRule(
    val groupTitle: String,    // "규정 1", "규정 2" 등
    val rules: List<FareRuleItem>
) : Serializable

9.3 FareRuleItem

소스: FareRuleItem.kt:7-24

data class FareRuleItem(
    val type: FareRuleType?,
    val title: String,
    val content: String?,
    val order: Int,
) : Serializable

9.4 StructuredFareRule

소스: StructuredFareRule.kt:6-15

data class StructuredFareRule(
    val detailKey: String,
    val fareRules: StructuredFareRuleDetail,
)

9.5 StructuredFareRuleDetail

소스: StructuredFareRule.kt:17-27

data class StructuredFareRuleDetail(
    val segments: List<SegmentFareRulePolicy>,
)

9.6 FareRulePolicy

소스: StructuredFareRule.kt:43-61

data class FareRulePolicy(
    val cancellation: CancellationPolicy?,
    val change: ChangePolicy?,
    val baggage: BaggagePolicy?,
    val mileage: MileagePolicy?,
    val additionalServices: AdditionalServicesPolicy?,
)

9.7 정책 세부 모델

모델필드소스
CancellationPolicyavailable, conditionsStructuredFareRule.kt:63-75
ChangePolicyavailable, conditionsStructuredFareRule.kt:77-89
PenaltyConditiontype, period, currency, feeStructuredFareRule.kt:91-107
BaggagePolicytype, baggagesStructuredFareRule.kt:109-121
MileagePolicyavailable, amount, rateStructuredFareRule.kt:123-137
AdditionalServicesPolicyseatSelectionAvailable, mealIncludedStructuredFareRule.kt:139-151

10. 응답 예시

10.1 운임 규정 그룹 조회 응답 예시

[
  {
    "detailKey": "uuid-key-1::cache-key-1::SUPPLIER1",
    "fareRules": [
      {
        "groupTitle": "규정 1",
        "rules": [
          {
            "type": "REFUND_AND_CHANGE",
            "title": "환불 규정",
            "content": "출발 7일 전까지 무료 환불 가능...",
            "order": 1
          },
          {
            "type": "BAGGAGE",
            "title": "수하물 규정",
            "content": "무료 위탁 수하물 23kg 1개...",
            "order": 2
          }
        ]
      }
    ]
  }
]

10.2 구조화된 운임 규정 조회 응답 예시

[
  {
    "detailKey": "uuid-key-1::cache-key-1::SUPPLIER1",
    "fareRules": {
      "segments": [
        {
          "id": "segment-1",
          "policy": {
            "cancellation": {
              "available": true,
              "conditions": [
                {
                  "type": "BEFORE_DEPARTURE",
                  "period": 7,
                  "currency": "KRW",
                  "fee": 50000
                }
              ]
            },
            "change": {
              "available": true,
              "conditions": [
                {
                  "type": "BEFORE_DEPARTURE",
                  "period": 3,
                  "currency": "KRW",
                  "fee": 30000
                }
              ]
            },
            "baggage": {
              "type": "WEIGHT",
              "baggages": [
                {
                  "unit": "KG",
                  "allowance": 23
                }
              ]
            },
            "mileage": {
              "available": true,
              "amount": 1500,
              "rate": 0.5
            },
            "additionalServices": {
              "seatSelectionAvailable": true,
              "mealIncluded": false
            }
          }
        }
      ]
    }
  }
]

부록: 소스 파일 위치 요약

파일경로
FareRuleProxyControllerinterfaces/controller/internal/proxy/FareRuleProxyController.kt
FareRuleControllerinterfaces/controller/FareRuleController.kt
FareRuleServiceapplication/FareRuleService.kt
AdapterClientinfrastructure/adapter/AdapterClient.kt
Constantssupport/Constants.kt
FareRulesViewinterfaces/response/FareRulesView.kt
FareRuledomain/FareRule.kt
FareRuleGroupdomain/FareRuleGroup.kt
FareRuleItemdomain/FareRuleItem.kt
StructuredFareRuledomain/StructuredFareRule.kt
FareRuleTypesupport/enums/FareRuleType.kt
SearchValidatorsupport/util/SearchValidator.kt
Exceptionssupport/exception/Exceptions.kt
MessageKeysupport/MessageKey.kt

문서 생성일: 2025-12-15 소스 기준: air-intl-search 프로젝트

관련 문서