퀵 레퍼런스

arch-overview config-build config-runtime

이 노트의 용도

매일 펼쳐 두는 “치트시트”다. 명령 한 줄, 파일 경로 하나가 급할 때 여기서 찾는다. 각 항목은 실제 소스(/workspace/nol/nol-triple/air-intl-adapter)에서 확인한 사실만 담았고, 더 깊은 설명은 우측 위키링크로 점프한다. 깊은 설정/배포 흐름은 build-deploy-config, configuration-and-infra, 실수하기 쉬운 함정은 landmines, 학습 순서는 onboarding-map 참고.


1. 핵심 명령 (Cheat Sheet)

자주 쓰는 명령 — 출처: build.gradle.kts, Dockerfile, CLAUDE.md

모든 명령은 프로젝트 루트 air-intl-adapter/에서 실행. 래퍼(./gradlew)를 쓰므로 Gradle 별도 설치 불필요.

목적명령비고 / 출처
빌드(테스트 포함)./gradlew build표준 Spring Boot 빌드
빌드(테스트 제외)./gradlew --exclude-task test buildCI(.github/workflows/ci.yml:34) · Docker 빌드(Dockerfile:12)가 쓰는 방식
로컬 실행(권장)./gradlew localBootRunbuild.gradle.kts:117-125에 정의된 커스텀 태스크. local,my 프로파일 자동 주입
임의 프로파일 실행./gradlew bootRun --args="--spring.profiles.active=dev,my"CLAUDE.md:20. 프로파일 콤마 조합
전체 테스트./gradlew testJUnit Platform (build.gradle.kts:113-115) + MockK
단일 테스트./gradlew test --tests "com.triple.air.intl.adapter.supplier.amadeus.application.AmadeusTest"FQCN 지정 (CLAUDE.md:26)
빌드 산출물build/libs/air-intl-adapter.jarDockerfile:30이 이 jar를 release 이미지로 복사
Docker 이미지 빌드docker build -t air-intl-adapter .멀티스테이지(base→build→release), 베이스 amazoncorretto:21

localBootRun의 함정 — build.gradle.kts:117-125

tasks.register("localBootRun") {
    group = "application"
    doFirst { tasks.bootRun { args("--spring.profiles.active=local,my") } }
    finalizedBy("bootRun")
}

doFirst 블록 안에서 bootRun.args(...)를 설정하고 finalizedBybootRun을 이어 실행하는 비표준 패턴이다. 즉 localBootRun 자체는 아무 것도 안 하고 bootRun을 뒤따라 돌리며 args만 주입한다. ./gradlew bootRun을 직접 부르면 프로파일이 비어 부팅에 실패할 수 있으니, 로컬은 반드시 localBootRun을 쓴다. 자세한 빌드 메커니즘은 build-deploy-config 참고.

포트 주의

컨테이너는 Dockerfile:32에서 EXPOSE 8080이지만, 앱이 실제로 바인딩하는 포트는 application.yml:5-6server.port: 8000이다. 로컬에서는 8000으로 접속(예: http://localhost:8000). 8080은 컨테이너 노출 메타데이터일 뿐이다.


2. 환경 셋업 순서

처음 로컬을 띄울 때 이 순서대로

핵심은 (1) JDK 21, (2) my 프로파일 파일 준비, (3) Redis 도달성, (4) AWS Secrets 접근 권한이다.

flowchart TD
    S0["STEP 0 사전 요구<br/>JDK 21 Amazon Corretto 권장<br/>Gradle 래퍼 동봉 별도 설치 불필요"]
    S1["STEP 1 프로파일 local,my<br/>localBootRun 이 두 프로파일 활성화<br/>local 은 application-local.yml 커밋됨<br/>my 는 application-my.yml 직접 생성 gitignore 됨"]
    S2["STEP 2 Redis Redisson<br/>local 프로파일은 redisson-local.yml 로드<br/>기본값이 DEV 원격 ElastiCache 를 가리킴<br/>도달 불가 시 my 로 로컬 Redis 오버라이드"]
    S3["STEP 3 AWS Secrets Manager<br/>local 프로파일도 dev 시크릿을 끌어옴<br/>공급사별 시크릿은 supplier yml 의 dev,local 섹션<br/>optional 접두사라 누락 시 런타임 실패<br/>AWS 자격증명이 셸 환경에 있어야 함"]
    S0 --> S1 --> S2 --> S3
  • STEP 0 — 사전 요구: JDK 21(Amazon Corretto 권장) — build.gradle.kts:21 (sourceCompatibility = VERSION_21, jvmTarget = JVM_21). Gradle 래퍼 동봉(./gradlew)이라 별도 설치 불필요.
  • STEP 1 — 프로파일 local,my: localBootRunlocal,my 두 개를 활성화. localapplication-local.yml(커밋됨), myapplication-my.yml(★ 직접 생성, .gitignore됨). .gitignore 마지막 줄 *-my.*가 모든 *-my.* 파일을 무시. 개인 비밀/오버라이드(포트, 자격증명 등)를 여기 둔다.
  • STEP 2 — Redis(Redisson): local 프로파일은 redisson/redisson-local.yml을 로드. application-local.yml:8-10에서 spring.redis.redisson.file: classpath:redisson/...-local. redisson-local.yml:11-12 → AWS ElastiCache serverless 주소 rediss://air-international-dev-...amazonaws.com:6379. ⚠ 기본값이 “DEV의 원격 Redis”를 가리킨다. VPN/네트워크 도달이 안 되면 application-my.yml로 로컬 Redis 오버라이드.
  • STEP 3 — AWS Secrets Manager: local 프로파일도 dev 시크릿을 끌어온다(application-local.yml:5-7): optional:aws-secretsmanager:dev/air-intl-adapter. 공급사별 시크릿은 supplier/*.ymldev,local 섹션에서 로드(예: supplier/amadeus.yml:1-7dev/air-intl-adapter/amadeus). optional: 접두사라 시크릿이 없어도 부팅은 되지만, 자격증명 누락 시 해당 공급사 호출이 런타임에 실패한다. AWS 자격증명(프로파일/역할)이 셸 환경에 있어야 한다.

my 프로파일이 뭐길래

application-my.yml은 저장소에 없다(.gitignore*-my.*가 차단). 각자 로컬에서 만들어 개인용 오버라이드(로컬 Redis, 슬랙 비활성, 포트 변경 등)를 둔다. application.yml:73-80에서 슬랙은 기본 active: true이고 application-local.yml:29-30active: false로 끄지만, 추가로 막고 싶거나 채널을 바꾸려면 my에서 한다. 프로파일/시크릿 전체 그림은 configuration-and-infra 참고.

프로파일별 Redis/Secrets 매핑 — 절대 헷갈리지 말 것

프로파일Redisson 파일AWS Secrets 경로(루트)
localredisson-local.ymldev/air-intl-adapter (★dev를 봄)
devredisson-dev.ymldev/air-intl-adapter
qaredisson-qa.ymlqa/air-intl-adapter
stagingredisson-staging.ymlstaging/air-intl-adapter
prodredisson-prod.ymlprod/air-intl-adapter
locallocal이 아니라 dev 시크릿/Redis를 보는 점이 신입이 가장 많이 빠지는 함정이다. 자세히는 landmines.

3. 중요 파일 위치

"그거 어디 있죠?" 표 — 모든 경로는 air-intl-adapter/ 기준 상대경로

절대경로 prefix: /workspace/nol/nol-triple/air-intl-adapter/

진입점 · 부팅

무엇경로메모
main / SpringBootApplicationsrc/main/kotlin/com/triple/air/intl/adapter/AirIntlAdapterApplication.kt@EnableRetry + @PostConstruct기본 TimeZone=UTC 강제 설정(line 12-13). 날짜 버그 추적 시 핵심
루트 패키지com.triple.air.intl.adaptergroup = com.triple.air.intl (build.gradle.kts:20)
빌드 스크립트build.gradle.kts의존성·localBootRun·컴파일 옵션
컨테이너Dockerfilerelease 베이스 titicacadev/amazoncorretto:21-ddtrace-latest (Datadog APM 내장)
SEED 암호화 라이브러리libs/TwaySEED.jarT’way 인증용 외부 jar(build.gradle.kts:67). 저장소에 바이너리 동봉

설정 (resources)

무엇경로메모
공통 설정src/main/resources/application.yml포트 8000, 공급사 yml import, Resilience4j 서킷브레이커, Slack/agent 채널
프로파일별src/main/resources/application-{local,dev,qa,staging,prod}.yml로깅·Redisson·시크릿 경로 분기
Redissonsrc/main/resources/redisson/redisson-{env}.ymlElastiCache serverless 클러스터 주소
로그백src/main/resources/logger/logback-{env}.xml + json-console-appender.xmlDatadog용 JSON 로그(logstash encoder)
공급사 시크릿 매핑src/main/resources/supplier/{amadeus,sabre,...,jejuair}.yml11개. 각 파일이 aws-secretsmanager:{env}/air-intl-adapter/{name} import
Jin Air 결제 인증서src/main/resources/supplier/jinair/*.ceraiRES_PYM_Cert.cer, payment.dev.cer

공급사 루트 (11개 모듈)

src/main/kotlin/com/triple/air/intl/adapter/supplier/
├── amadeus        (GDS/SOAP · TOPAS 1A · 최대 모듈 · stateful PNR)  → [[amadeus-overview]]
├── sabre          (GDS/SOAP · 1S · 코루틴 SabrePassengerService)    → [[sabre-overview]]
├── galileo        (GDS/SOAP · 1G · Universal API · 스키마 방대)     → [[galileo-overview]]
├── amadeusndc     (NDC · ART 클라이언트)                            → [[amadeusndc-overview]]
├── tway           (LCC/REST · TwaySEED.jar SEED 암호화)             → [[tway-overview]]
├── jinair         (LCC/REST · 부가서비스/대리점 크레딧)            → [[jinair-overview]]
├── koreanair      (NDC V21.3)                                       → [[koreanair-overview]]
├── lufthansa      (NDC V17.2 · certification 재발행)                → [[lufthansa-overview]]
├── singaporeair   (NDC EDIST 18.1)                                  → [[singaporeair-overview]]
├── jejuair        (LCC/REST · 취소 코루틴)                          → [[jejuair-overview]]
└── groupair       (그룹/단체 운임 · 최소 모듈)                      → [[groupair-overview]]

공급사 모듈 내부 구조 (모두 동일 패턴)

supplier/{name}/
├── interfaces/controller/internals/{Name}{Op}Controller.kt  ← 내부 REST 진입점
├── interfaces/  (request/response DTO)
├── application/ ({Name}{Op}Service)
├── infrastructure/ (외부 API 클라이언트, req/res 모델)
├── domain/  (도메인 모델/리포지토리)
├── support/ (공급사 전용 유틸/enum)
└── configuration/

중앙 디스패처가 없다. 각 컨트롤러가 @RequestMapping("/internals/{NAME}/{op}")로 직접 노출된다. 예: AmadeusSearchController.kt:18-19/internals/AMADEUS/search + @CircuitBreaker(name="amadeusSearch"). 라우팅 전체 지도는 caller-callee-map, 공통 오퍼레이션은 common-operations.

스키마 (XSD) · 목 데이터 (test 전용)

무엇경로메모
공급사 XSD 스키마src/test/schema/{galileo,koreanair,lufthansa,singaporeair,amadeus,sabre,jinair}/src/test 아래에 있음(런타임 아님). galileo가 universal_v52_0 등 12개 하위로 가장 방대
Korean Air NDCsrc/test/schema/koreanair/NDC_V21.3_Schema_V2025.2/NDC V21.3
Lufthansa NDCsrc/test/schema/lufthansa/NDC_V17.2_Schema_V2023.3/ + certification/truereshop*재발행(reshop) 인증 시나리오
Singapore EDISTsrc/test/schema/singaporeair/18_1_EDIST_schemas/ + example/재발행Sample/EDIST 18.1 + 재발행 샘플
목 응답 데이터src/test/resources/mockData/{amadeus,sabre,lufthansa}/테스트용 XML 픽스처. 예) sabre/GetReservationRS_EOF_ERROR.xml, lufthansa/LH-OrderReshopRS.xml
공통 목src/test/resources/mockData/Queue_CountTotalReply_numberOfItems1.xml루트 직속 픽스처

공통(support) 핵심 파일

무엇경로노트
코루틴 헬퍼support/util/CoroutineExtensions.ktwithBlocking/withLaunch/withAsync/pmap. SupervisorJob+MDCContext 합성
코루틴 예외 처리support/exception/AdapterCoroutineExceptionHandler.kt루트 원인 추출 후 Sentry 로깅
REST 예외 처리support/exception/RestExceptionHandler.kt전역 핸들러
에러 코드support/exception/ErrorMessage.ktenum (SEARCH_FAILED, BOOKING_FAILED, REPRICING_FAILED …)
Slack 경보application/SlackService.ktemergency/operation 채널 분리 발송
캐시 키support/cache/CacheKeyGenerator.kt공급사+요청 기반 키 생성
Redis 직렬화support/cache/{Gzip,Snappy}RedisSerializer.kt대용량 객체 압축

4. 자주 겪는 문제 → 확인 위치 → 노트

증상별 디버깅 진입표

막혔을 때 “증상” 행을 찾아 “1차 확인 위치”부터 본다. 디버깅 시나리오 연습은 exercises-debugging.

증상1차 확인 위치원인/메모노트
bootRun이 프로파일 없이 떠서 실패build.gradle.kts:117-125로컬은 localBootRun 써야 local,my 주입됨build-deploy-config
application-my.yml not found.gitignore *-my.*my 프로파일 파일을 직접 생성해야 함(저장소에 없음)configuration-and-infra
로컬 포트로 접속 안 됨application.yml:5-6 vs Dockerfile:32앱은 8000, EXPOSE는 8080(메타데이터)configuration-and-infra
Redis 연결 타임아웃redisson-local.yml:11-12, application-local.yml:8-10기본이 DEV 원격 ElastiCache. 네트워크 도달 안 되면 my로 로컬 오버라이드configuration-and-infra
특정 공급사만 인증/호출 실패supplier/{name}.yml + AWS Secrets {env}/air-intl-adapter/{name}optional: import라 시크릿 없으면 조용히 누락 → 런타임 실패landmines
검색은 되는데 빈 결과만 옴컨트롤러 searchFallback (예: AmadeusSearchController.kt:72-80)서킷 OPEN 시 빈 리스트 반환(예외 아님). Datadog span 태그 supplier.circuit-breaker=OPEN 확인resilience-and-events
서킷이 자꾸 열림application.yml:37-70search config: window 180s/TIME_BASED, 최소 30콜, 실패율 35%, open 120sresilience-and-events
비동기 호출이 통째로 죽음support/util/CoroutineExtensions.ktSupervisorJob으로 자식 격리, pmap은 개별 실패를 AsyncFail로 수집async-coroutines
예외가 로그/Sentry에만 남고 던져지지 않음AdapterCoroutineExceptionHandler.kt:15-25코루틴 핸들러가 root cause 로깅+Sentry만, 재던지지 않음error-handling
날짜/시간 1일 어긋남AirIntlAdapterApplication.kt:12-13부팅 시 JVM 기본 TZ를 UTC로 강제. LocalDateTime 가정 주의error-handling
에러 코드 의미 모름support/exception/ErrorMessage.ktenum 상수 목록(예약/발권/재발행 단계별)error-handling
Slack 경보가 안 옴application.yml:73-80, application-local.yml:29-30local은 slack.active=false(기본 true)resilience-and-events
배포가 안 나감.github/workflows/cd.yml (tags: release-**)release-* 태그 push로만 트리거. master push는 tag.ymlrelease-qa 자동 태깅build-deploy-config
테스트만 빼고 빌드하고 싶음ci.yml:34, Dockerfile:12./gradlew --exclude-task test buildbuild-deploy-config

빠른 점프

더 깊이 들어가려면