FareRuleController DFS (기능명세서)

1. API 개요 및 목적

1.1 개요

항목
컨트롤러FareRuleController
기본 경로/fare-rules
소스 위치air-intl-search/.../interfaces/controller/FareRuleController.kt:17-67
목적국제 항공권의 운임 규정(Fare Rule) 조회

1.2 주요 기능

  • 비동기 폴링 방식: 키 발급 후 별도 조회하는 2단계 API 구조
  • 운임 규정 조회: 환불/변경 정책, 수하물 정보, 마일리지 등 조회
  • 공급자(Supplier) 연동: AMADEUS 등 외부 항공 데이터 공급자를 통한 규정 조회

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

2.1 운임 규정 키 발급 API

항목
엔드포인트GET /fare-rules
메서드getFareRuleKey
소스 위치FareRuleController.kt:22-44
HTTP 상태202 Accepted

Request Parameters

파라미터타입필수기본값설명
flightDetailKeyStringO-항공편 상세 키
adultIntO-성인 승객 수
childIntX0소아 승객 수
infantIntX0유아 승객 수

소스: FareRuleController.kt:23-27

Response

{
  "key": "UUID"
}
필드타입설명
keyUUID운임 규정 조회용 캐시 키 (TTL: 20분)

소스: FareRuleController.kt:33-43, FareRuleRepository.kt:14

처리 흐름

sequenceDiagram
    participant Client
    participant Controller
    participant Service
    participant Repository
    participant AdapterClient
    participant ExternalAPI

    Client->>Controller: GET /fare-rules
    Controller->>Controller: checkMaintenanceTime(supplier)
    Controller->>Controller: checkSearchablePassengers()
    Controller->>Service: init(detailKey, adult, child, infant)
    Service->>Service: CacheKeyGenerator.getFareRuleCacheKey()
    Service->>Repository: save(key, PENDING)
    Service-->>Controller: return UUID
    Controller-->>Client: 202 Accepted {key: UUID}

    Note over Service,ExternalAPI: 비동기 처리 (Coroutine)
    Service->>AdapterClient: getFareRules()
    AdapterClient->>ExternalAPI: GET /{supplier}/fare-rules
    ExternalAPI-->>AdapterClient: FareRuleResponse[]
    AdapterClient-->>Service: List<FareRule>
    Service->>Repository: save(key, COMPLETE, fareRules)

2.2 운임 규정 조회 API

항목
엔드포인트GET /fare-rules/{fareRuleKey}
메서드getFareRule
소스 위치FareRuleController.kt:46-66
HTTP 상태200 OK / 202 Accepted / 500 Internal Server Error

Path Parameters

파라미터타입설명
fareRuleKeyUUID키 발급 API에서 받은 캐시 키

소스: FareRuleController.kt:47

Response

{
  "status": "COMPLETE",
  "fareRules": [
    {
      "groupTitle": "규정 1",
      "rules": [
        {
          "type": "REFUND_AND_CHANGE",
          "title": "환불/변경 규정",
          "content": "규정 상세 내용",
          "order": 1
        }
      ]
    }
  ]
}

소스: FareRulesView.kt:9-42


3. Request/Response 구조

3.1 Response DTOs

FareRuleInfoView

필드타입Nullable설명
statusPollingStatusX조회 상태
fareRulesList<FareRuleView>O운임 규정 목록

소스: FareRulesView.kt:9-12

FareRuleView

필드타입설명
groupTitleString규정 그룹 제목 (예: “규정 1”)
rulesList<FareRuleItemView>규정 항목 목록

소스: FareRulesView.kt:14-25

FareRuleItemView

필드타입Nullable설명
typeFareRuleTypeO규정 유형
titleStringX규정 제목
contentStringO규정 내용
orderIntX정렬 순서

소스: FareRulesView.kt:27-42

3.2 Enum 정의

PollingStatus

설명HTTP 상태
PENDING처리 중202 Accepted
COMPLETE완료200 OK
ERROR에러 발생500 Internal Server Error
NO_DATA데이터 없음200 OK

소스: PollingStatus.kt:3-8

FareRuleType

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

소스: FareRuleType.kt:3-8


4. 비동기 폴링 방식 분석

4.1 폴링 아키텍처

stateDiagram-v2
    [*] --> PENDING: 키 발급 요청
    PENDING --> COMPLETE: 외부 API 호출 성공
    PENDING --> ERROR: 외부 API 호출 실패
    COMPLETE --> [*]: 규정 반환
    ERROR --> [*]: 예외 발생

4.2 구현 상세

초기화 단계 (init)

// FareRuleService.kt:81-113
fun init(detailKey: String, adult: Int, child: Int, infant: Int): UUID {
    return CacheKeyGenerator.getFareRuleCacheKey().also { fareRuleKey ->
        // 1. PENDING 상태로 캐시 저장
        fareRuleRepository.save(
            key = fareRuleKey,
            fareRuleInfo = FareRuleInfo(status = PollingStatus.PENDING)
        )
        // 2. 비동기로 외부 API 호출
        CoroutineScope(Dispatchers.IO).withLaunch {
            try {
                // 성공 시 COMPLETE 상태로 업데이트
                fareRuleRepository.save(...)
            } catch (e: Exception) {
                // 실패 시 ERROR 상태로 업데이트
                fareRuleRepository.save(...)
            }
        }
    }
}

소스: FareRuleService.kt:81-113

캐시 저장소

항목
저장소Redis
키 패턴international-fare-rule-key::{UUID}
TTL20분

소스: FareRuleRepository.kt:13-14

4.3 클라이언트 폴링 패턴

1. Client -> GET /fare-rules?... -> 202 Accepted {key: UUID}
2. Client -> GET /fare-rules/{key} -> 202 Accepted (PENDING)
3. Client -> GET /fare-rules/{key} -> 202 Accepted (PENDING)
4. Client -> GET /fare-rules/{key} -> 200 OK (COMPLETE) + fareRules

5. 비즈니스 로직 흐름

5.1 전처리 검증

flightDetailKey 파싱

// StringUtils.kt:4-9
// 형식: {listKey}::{supplier}_{key}::{기타}
// 예시: ef4706bc-cbaa-4f73-86e3-9d578ca6a01d::AMADEUS_04633252-60d4-43fb-a372-5adec32034cf::1_1_1
fun String.destructDetailKey(): Triple<String, String, String> {
    val listKey = this.substringBefore("::")
    val key = this.substringAfter("::")
    val supplier = key.substringBefore("_").uppercase()
    return Triple(listKey, key, supplier)
}

소스: StringUtils.kt:4-9

점검 시간 확인

// Maintenance.kt:5-9
fun checkMaintenanceTime(supplier: String) {
    when (supplier) {
        "AMADEUS" -> Unit  // 현재 특별 처리 없음
    }
}

소스: Maintenance.kt:5-9

승객 수 검증

규칙조건에러 메시지 키
성인 필수adult < 1INVALID_PASSENGERS
총 인원 제한adult + child + infant > 9INVALID_PASSENGERS_COUNT
소아 제한child > adult * 3INVALID_PASSENGERS_CHILD
유아 제한adult < infantINVALID_PASSENGERS_INFANT

소스: SearchValidator.kt:6-30

5.2 Service Layer

FareRuleService 주요 메서드

메서드설명소스 위치
init()비동기 조회 초기화 및 캐시 키 반환FareRuleService.kt:81-113
getFareRules(UUID)캐시에서 결과 조회FareRuleService.kt:76-79
getFareRules(detailKey,...)동기식 외부 API 호출FareRuleService.kt:26-35

5.3 외부 API 연동

AdapterClient 호출

// AdapterClient.kt:118-154
fun getFareRules(
    key: String,
    supplier: String,
    adult: Int,
    child: Int,
    infant: Int,
): List<FareRule>
항목
엔드포인트{endpoint}/{supplier}/fare-rules
HTTP MethodGET
Query Paramskey, adult, child, infant

소스: AdapterClient.kt:118-154


6. 에러 처리 및 예외 상황

6.1 예외 유형별 처리

예외 클래스HTTP 상태메시지 키발생 조건
MethodArgumentInvalidException400INVALID_PASSENGERS*승객 수 유효성 검증 실패
CacheKeyInvalidException410INVALID_CACHE_KEY캐시 키 만료 또는 없음
InternationalSearchException500SOLD_OUT좌석 매진
InternationalSearchException500FARE_RULE_FAILED운임 규정 조회 실패

6.2 SOLD_OUT 처리

// FareRuleController.kt:54-60
PollingStatus.ERROR -> {
    when (fareRuleInfo.exception?.message?.substringBefore(":")) {
        "SOLD_OUT" -> throw InternationalSearchException(MessageKey.SOLD_OUT)
        else -> throw InternationalSearchException(MessageKey.FARE_RULE_FAILED)
    }
}

소스: FareRuleController.kt:56-59

SOLD_OUT 발생 경로

flowchart TD
    A[AdapterClient.getFareRules] --> B{외부 API 응답}
    B -->|성공| C[FareRule 반환]
    B -->|실패| D{에러 코드 확인}
    D -->|SOLD_OUT| E[InternationalSearchException<br/>SOLD_OUT]
    D -->|기타| F[InternationalSearchException<br/>FARE_RULE_FAILED]
    E --> G[FareRuleService.init catch]
    F --> G
    G --> H[Repository.save<br/>ERROR 상태]

소스: AdapterClient.kt:147-153

6.3 에러 응답 형식

{
  "status": 500,
  "code": "SOLD_OUT",
  "title": "에러 제목",
  "message": "에러 메시지",
  "messageArguments": []
}

소스: ErrorView.kt:3-27

6.4 예외 핸들러 매핑

예외핸들러HTTP 상태소스 위치
MethodArgumentInvalidExceptionRestExceptionHandler.handle()400RestExceptionHandler.kt:64-80
CacheKeyInvalidExceptionRestExceptionHandler.handle()410RestExceptionHandler.kt:100-116
InternationalSearchExceptionRestExceptionHandler.handle()500RestExceptionHandler.kt:132-148

7. 관련 도메인 모델 분석

7.1 도메인 클래스 구조

classDiagram
    class FareRuleInfo {
        +PollingStatus status
        +Throwable? exception
        +List~FareRule~? fareRules
    }

    class FareRule {
        +String groupTitle
        +List~FareRuleItem~ rules
        +of(sequence, ruleItems)
    }

    class FareRuleItem {
        +FareRuleType? type
        +String title
        +String? content
        +Int order
        +of(FareRuleResponse)
    }

    class FareRuleResponse {
        +Int groupSequence
        +Int ordered
        +String? category
        +FareRuleType? type
        +String title
        +String? contents
    }

    FareRuleInfo --> FareRule
    FareRule --> FareRuleItem
    FareRuleItem ..> FareRuleResponse : from

7.2 도메인 모델 상세

FareRuleInfo

필드타입설명소스 위치
statusPollingStatus처리 상태FareRuleInfo.kt:8
exceptionThrowable?발생한 예외FareRuleInfo.kt:9
fareRulesList<FareRule>?운임 규정 목록FareRuleInfo.kt:10

소스: FareRuleInfo.kt:7-15

FareRule

필드타입설명소스 위치
groupTitleString그룹 제목 (예: “규정 1”)FareRule.kt:8
rulesList<FareRuleItem>규정 항목 목록FareRule.kt:9

소스: FareRule.kt:7-21

FareRuleItem

필드타입설명소스 위치
typeFareRuleType?규정 유형FareRuleItem.kt:8
titleString규정 제목FareRuleItem.kt:9
contentString?규정 내용FareRuleItem.kt:10
orderInt정렬 순서FareRuleItem.kt:11

소스: FareRuleItem.kt:7-24

7.3 Repository

FareRuleRepository

항목
저장소 타입Redis
템플릿RedisTemplate<String, FareRuleInfo>
키 접두사international-fare-rule-key
TTL20분
메서드설명소스 위치
save(key, fareRuleInfo)캐시에 저장FareRuleRepository.kt:17-22
find(key)캐시에서 조회FareRuleRepository.kt:25-27

소스: FareRuleRepository.kt:9-32


8. 소스 파일 참조

파일경로
FareRuleControllerair-intl-search/.../interfaces/controller/FareRuleController.kt
FareRuleServiceair-intl-search/.../application/FareRuleService.kt
FareRuleRepositoryair-intl-search/.../domain/repository/FareRuleRepository.kt
FareRuleInfoair-intl-search/.../domain/FareRuleInfo.kt
FareRuleair-intl-search/.../domain/FareRule.kt
FareRuleItemair-intl-search/.../domain/FareRuleItem.kt
FareRulesViewair-intl-search/.../interfaces/response/FareRulesView.kt
AdapterClientair-intl-search/.../infrastructure/adapter/AdapterClient.kt
PollingStatusair-intl-search/.../support/enums/PollingStatus.kt
FareRuleTypeair-intl-search/.../support/enums/FareRuleType.kt
Exceptionsair-intl-search/.../support/exception/Exceptions.kt
RestExceptionHandlerair-intl-search/.../support/exception/RestExceptionHandler.kt
SearchValidatorair-intl-search/.../support/util/SearchValidator.kt
StringUtilsair-intl-search/.../support/util/StringUtils.kt
Maintenanceair-intl-search/.../support/util/Maintenance.kt
CacheKeyGeneratorair-intl-search/.../support/cache/CacheKeyGenerator.kt

관련 문서