외부 연동 정비 TDD
Background
근거 PRD: /Users/biuea/Desktop/dpdpdndn/프로젝트/스포츠앱/외부 연동 정비/PRD.md (검수 PASS)
선행 ① 산출물 인용: ../도메인 경계 재설계/TDD.md, ../도메인 경계 재설계/ADR/ADR-002 도메인 관계 유형과 허용 의존 방향.md
sports-application은 외부 Open API를 이미 domain interface + infrastructure 구현 구조(Gateway 패턴)로 추상화했고, base-url+api-key를 env로 받아 코드 변경 없이 mock↔실연동을 전환하는 메커니즘이 동작 중이다(application.yml:132-148). 이 과제는 새 구현체 개발이 아니라 발급 주체별 무료 한도 조사 → 전환 판정 → 키 발급·env 설정 → 실 응답이 mock 스키마와 일치하는지 검증이다. 코드 변경은 최소(주로 설정·검증)로 제한한다.
Overview
- 무엇을: (1) 발급 주체 3곳(카카오·공공데이터포털·SOLAPI) 무료 한도 조사표, (2) 전환 판정(일 1,000건 임계) 적용 결과, (3) 실 응답↔mock 스키마 일치를 자동 감시하는 계약 검증 테스트 하네스, (4) mock 영구 유지 분류(SOLAPI·PG), (5) 신규 외부 데이터 소스 3개+ 발굴.
- 왜: 전환 메커니즘은 존재하지만 “실행”된 적이 없고, 실 API 스키마 회귀를 감시하는 수단이 없다. 키를 발급해 env를 켠 뒤 실 응답이 mock DTO와 어긋나면 조용히 빈 결과가 반환된다(facility/geocoding은 graceful degradation).
- 어떻게: 실 API를 CI에서 상시 호출하지 않는다(quota·키 노출 리스크). MockWebServer + 녹화 fixture로 Gateway HTTP 경로·역직렬화를 CI에서 상시 검증(스키마 락)하고, 실 키가 있을 때만 도는
live태그 스모크 테스트를 opt-in gradle task로 분리해 1회성 실 응답 검증에 사용한다. 조사표·발굴·mock 유지 사유는 본 문서(Detail Design)가 SSOT다.
Terminology
| 용어 | 정의 |
|---|---|
| 발급 주체(Issuer) | API 키를 발급하는 포털/벤더 단위 — 카카오, 공공데이터포털, SOLAPI. 조사·전환 판정 단위 |
| 전환 판정 임계 | 무료 API 일일 요청 한도 ≥ 1,000건이면 실연동 전환, 미만이면 mock 유지(PRD FR-2) |
| 계약 검증(Contract Verification) | 실 API 응답 스키마가 Gateway DTO(mock 스키마 기준)와 일치하는지 검증하는 테스트 |
| 녹화 fixture | 실 API(또는 스키마 동일 mock) 응답 JSON을 1회 캡처해 CI 계약 테스트의 입력으로 고정한 파일 |
| live 태그 | 실 키가 env에 있을 때만 실 API를 호출하는 스모크 테스트에 부여하는 태그. 기본 test에서 제외 |
| env 스위치 | base-url+api-key 환경변수. 키 부재→mock host(localhost:910x), 존재→실 API host |
| generic subdomain | 자체 소유 Entity 없이 외부 데이터를 조회·반환만 하는 도메인(weather) — ① TDD 분류 인용 |
Define Problem
AS-IS
실제 코드(backend/src/main/kotlin/com/sportsapp/)를 읽어 확인.
전환 메커니즘 (동작 중): application.yml:132-148에 4개 외부 config.
| config 블록 | base-url env (기본값) | api-key env (기본값) | 대응 GatewayImpl |
|---|---|---|---|
external.geocoding (134) | EXTERNAL_GEOCODING_BASE_URL (http://localhost:9101) | KAKAO_REST_API_KEY (빈값) | KakaoGeocodingGatewayImpl |
external.public-facility (136) | EXTERNAL_PUBLIC_FACILITY_BASE_URL (http://localhost:9102) | DATA_GO_KR_SERVICE_KEY (mock-service-key) | DataGoKrPublicFacilityGatewayImpl |
external.weather (139) | EXTERNAL_WEATHER_BASE_URL (http://localhost:9102) | DATA_GO_KR_SERVICE_KEY (빈값) | KmaWeatherGatewayImpl |
external.sms (146) | EXTERNAL_SMS_BASE_URL (http://localhost:9103) | SOLAPI_API_KEY (mock-api-key) | SmsChannelGateway |
- 각 GatewayImpl은
ExternalRestClientFactory.create(baseUrl)로 RestClient를 생성한다. Client는 GatewayImpl에만 DI됨(컨벤션 준수). 타임아웃 강제(connect 3s / read 5s,ExternalRestClientFactory.kt:28-29). - facility·weather 키 공유 확인:
public-facility.api-key(138)와weather.api-key(141)가 동일${DATA_GO_KR_SERVICE_KEY}. 키 1건으로 두 도메인 동시 전환(PRD FR-3). 단 기본값 비대칭 — public-facility는mock-service-key, weather는 빈값.
실패 경로 불일치 (실측):
| GatewayImpl | 외부 오류 처리 | 근거 |
|---|---|---|
KakaoGeocodingGatewayImpl | try/catch(RestClientException) → null 반환(graceful) | KakaoGeocodingGatewayImpl.kt:42-45 |
DataGoKrPublicFacilityGatewayImpl | try/catch(RestClientException) → emptyList() 반환(graceful) | DataGoKrPublicFacilityGatewayImpl.kt:39-42 |
KmaWeatherGatewayImpl | catch 없음 → 예외 그대로 전파 | KmaWeatherGatewayImpl.kt:31-46 (try/catch 부재) |
→ 실 API 전환 시 기상청이 5xx/timeout을 내면 weather UseCase가 예외로 깨진다(facility/geocoding은 degrade). 전환 대상 도메인 간 실패 정책 불일치.
검증 수단 부재: Gateway HTTP 경로 통합 테스트가 없다. weather/gateway 테스트는 GridConverterTest.kt(좌표 변환 단위)뿐. facility gateway 통합 테스트 0건. 실 응답↔mock 스키마 회귀를 감시하는 테스트 없음. build.gradle.kts에 MockWebServer/WireMock 의존성 없음(Testcontainers mysql/mongodb/kafka만).
mock 서버 (스키마 정확): mock-servers/{kakao-local,data-go-kr,solapi}가 실 API 스키마를 모사. data-go-kr은 공공체육시설(/openapi/service/publicSportsFacility/getList) + 기상청 단기예보(/getVilageFcst) 2종을 동일 호스트로 서빙. mock-servers/README.md에 전환표 존재.
문제점:
- 발급 주체별 무료 한도·인증·전환 가부가 수치로 정리된 적 없음.
- 실 응답 스키마 회귀를 자동 감시하는 계약 테스트 없음 → 전환 후 조용한 빈 결과 리스크.
- weather 실패 경로가 나머지 두 gateway와 불일치.
- env 스위치 기본값 비대칭(weather api-key 빈값) — 전환표와 문서화 부재.
- 신규 외부 데이터 소스 발굴 미수행(PRD FR-4).
TO-BE
- 발급 주체 3곳 조사표(존재·인증·일 한도·요금·상업 활용) + 전환 판정 결과.
- MockWebServer + 녹화 fixture 계약 테스트로 3개 실연동 대상(공공체육시설·기상청 단기예보·카카오 지오코딩) 스키마를 CI에서 상시 검증(스키마 락).
- 실 키가 있을 때만 도는
live태그 스모크 테스트 +verifyExternalLivegradle task로 1회성 실 응답 검증. - weather 실패 경로를 facility/geocoding과 동일 graceful degradation으로 정합.
- env 스위치 기본값 정합 + 전환 runbook·
.env.sample문서화. - mock 영구 유지 분류(SOLAPI·PG) 사유표 + 신규 데이터 소스 3개+ 발굴표.
Architecture Benchmarking (의무)
| 제품/사례 | 해결 방식 | 참고할 패턴 | 미참고 사유 |
|---|---|---|---|
| Twelve-Factor App — Config (12factor.net/config) | 환경별 설정을 코드가 아니라 env에 두고, 배포마다 값만 교체. 코드 무변경으로 backing service 전환 | ① base-url+api-key를 env로 두고 코드 무변경 전환하는 AS-IS 원칙을 정당화 → 현상 유지 채택. ② mock↔real은 “같은 계약을 만족하는 attached resource 교체”로 해석 | 12factor는 런타임 hot-reload를 규정하지 않음 — 우리도 env 반영은 롤링 재기동 수용(Release Scenario) |
| Consumer-Driven Contract / Pact + WireMock (pact.io, Baeldung Pact) | 소비자 기대를 concrete request/response 예시(contract-by-example)로 고정하고, provider 응답이 계약을 만족하는지 검증 | ① “contract-by-example” — 실 응답 1건을 녹화 fixture로 고정해 스키마 회귀를 테스트로 락. ② WireMock류 로컬 HTTP 스텁으로 Gateway HTTP 경로·역직렬화를 실 네트워크 없이 검증 | Pact broker·provider verification(양방향 계약 인프라)은 개인 모놀리스 + 통제 불가 정부/벤더 API 상대로 과함 → 단방향 fixture 계약만 차용 |
| Spring Cloud Contract stub-runner (spring.io/projects/spring-cloud-contract) | 계약 DSL로 stub을 생성·공유해 소비자/공급자 양쪽 검증 | 계약을 코드로 실행 가능하게 만드는 fitness-function 관점만 참고 | stub 저장소·플러그인·Groovy DSL 체계가 무겁다. MockWebServer 직접 사용이 더 단순(단순함 우선) → 미채택 |
Possible Solutions
방안 비교
| 방안 | 설명 | 왜 채택 / 미채택 |
|---|---|---|
| A. 순수 문서 + 수동 curl 검증 | 조사표·runbook만 남기고 실 응답 검증은 수동 curl로 1회 | 부분 채택 — 조사표·발굴·mock 유지 사유는 문서(본 TDD)가 SSOT. 단 수동 검증만으론 스키마 회귀를 지속 감시 못함 → B 병행 |
B. MockWebServer 녹화 fixture 계약 테스트(CI 상시) + live 태그 실 API 스모크(opt-in gradle task) | fixture로 Gateway HTTP·역직렬화·매핑을 CI에서 상시 검증(키 불요). 실 키가 env에 있을 때만 verifyExternalLive task로 실 API 1회 호출해 동일 DTO 무손실 역직렬화 확인 | 채택 — 스키마 락(회귀 감시) + 저빈도 실 검증(PRD NFR 정합). CI에 키 없음·quota 소모 없음. contract-by-example 벤치마크와 정합 |
| C. Pact broker + provider verification | 계약 브로커에 pact 등록, provider CI가 검증 | 미채택 — 정부/벤더 API는 우리가 provider CI를 붙일 수 없다. 브로커 인프라도 개인 프로젝트에 과함(단순함 우선) |
| D. WireMock record proxy 상시 캡처 | 실 API 앞에 record proxy를 상시 두고 응답을 자동 캡처 | 부분 참고 — fixture는 실 응답 1회 캡처로 seed(권장). 단 상시 proxy 운영은 복잡·불필요 → 미채택 |
| E. 실 API를 CI에서 상시 호출 | 매 빌드가 실 API를 호출해 응답 검증 | 미채택 — quota 소모, CI에 키 노출, 정부 API 장애 시 플레이키. PRD NFR(“저빈도 호출”, “키 평문 노출 0”) 위반 |
단순함 우선 결론: A(문서 SSOT) + B(MockWebServer fixture 계약 테스트 + live 태그 opt-in). C·D·E는 과하거나 NFR 위반으로 미채택.
Detail Design
발급 주체 무료 API 전환 조사표 (FR-1, FR-2)
전환 판정 임계 = 무료 일일 요청 한도 ≥ 1,000건(PRD FR-2). 수치는 2026-07 조사 기준.
| 발급 주체 | API (서비스) | 존재 | 인증 방식 | 무료 일 한도 | 요금(초과) | 상업 활용 | 판정 |
|---|---|---|---|---|---|---|---|
| 카카오 | Local REST — 주소→좌표 (/v2/local/search/address.json) | O | Authorization: KakaoAK {REST키} (header) | 100,000/day (좌표변환) | 초과분 유료(프로모션 건당 10원, 2026) | 조건부 허용 | 전환 (≥1,000) |
| 공공데이터포털 | 기상청 단기예보 getVilageFcst | O | serviceKey (query) | 개발계정 10,000/day | 운영계정 승인 시 증량 | 조건부 허용 | 전환 (≥1,000) |
| 공공데이터포털 | 공공체육시설 목록 getList | O | serviceKey (동일 키 공유) | 개발계정 10,000/day 통상 | 운영계정 승인 시 증량 | 조건부 허용 | 전환 (동일 키) |
| SOLAPI | SMS /messages/v4/send | O | Authorization (API키) | 한도 개념 아님(발송 건당 과금) | 건당 과금 + 발신번호 사전등록 | 발신번호 필요 | mock 유지 |
판정 결과: 카카오(지오코딩 100k/day) + 공공데이터포털(단기예보·체육시설 각 10k/day) → 3개 서비스 전환. 공공데이터포털 키 1건 발급으로 facility·weather 동시 전환(FR-3). SOLAPI는 아래 mock 유지 표로.
참고 출처: Kakao Developers 쿼터, 공공데이터포털 기상청 단기예보, SOLAPI 발신번호.
mock 영구 유지 분류 (FR-5)
| 항목 | mock 유지 사유 | 재검토 조건 |
|---|---|---|
| SOLAPI SMS | 발신번호 사전등록 필요(개인 명의 인증 가능하나 실발송 건당 과금), 2026 스팸 발신번호 차단 정책, 개인 프로젝트 실발송 실익 없음 | 사업자 발신번호 확보 시 |
| PG 6사 (kakao·toss·naver·danal·bank_transfer·card) | 실 결제 연동에 사업자 가맹 계약 필요 — 개인 프로젝트 불가(PRD Non-Goals) | 사업자 계약 시 |
신규 외부 데이터 소스 발굴 (FR-4) — 발급 주체 재사용 우위 강조
3개 모두 공공데이터포털 발급 주체 → 이미 발급한 계정으로 활용신청만 추가하면 됨(신규 발급 주체 온보딩 불요). PRD Non-Goals에 따라 발굴·문서화까지이며 구현은 별도 과제.
| # | 소스 (제공기관) | 발급 주체 | 무료 일 한도 | 스포츠앱 유즈케이스 연계 | 상태 |
|---|---|---|---|---|---|
| 1 | 에어코리아 대기오염정보 (한국환경공단) | 공공데이터포털 | 개발계정 500/day | 야외 시설(축구장·테니스장) 조회/예약 시 미세먼지 지수 노출 → weather 도메인 부가정보 또는 facility 상세 보강 | 발굴 대기 |
| 2 | 전국체육시설 상세정보 (국민체육진흥공단, 15113986) | 공공데이터포털 | 개발계정 통상 10,000/day | 현재 공공체육시설 목록보다 상세(면적·수용인원·좌석·관리기관) → facility Aggregate 상세 보강 | 발굴 대기 |
| 3 | 기상청 초단기실황 getUltraSrtNcst (기상청) | 공공데이터포털 | 개발계정 10,000/day | 단기예보(6시간 단위)를 보완하는 실시간 현재 날씨 → weather 조회 정밀화. 기존 단기예보와 동일 계열 서비스로 키 재사용 | 발굴 대기 |
| (보너스) | 생활체육 강좌정보 (국민체육진흥공단) | 공공데이터포털 | 개발계정 | 지역 생활체육 강좌 → post/booking 확장 아이디어 | 발굴 대기 |
참고 출처: 에어코리아 대기오염정보, 국민체육진흥공단 전국체육시설 정보.
시스템 역할 경계 (의무)
신규 도메인·신규 서버 없음. 기존 facility·weather infrastructure + 공통 test 하네스에 작업이 합류한다.
| 단위 | 역할 | 소유 데이터/책임 | 노출 인터페이스 | 의존 |
|---|---|---|---|---|
KakaoGeocodingGatewayImpl (infra/facility) | 주소→좌표 (실/mock 전환) | 무소유(조회) | GeocodingGateway.geocode(address) | RestClient, GeocodingProperties |
DataGoKrPublicFacilityGatewayImpl (infra/facility) | 공공체육시설 목록 조회 | 무소유(조회) | PublicSportsFacilityGateway.fetchPage(pageNo,numOfRows) | RestClient, PublicFacilityProperties |
KmaWeatherGatewayImpl (infra/weather) | 단기예보 조회 (실패 경로 정합 대상) | 무소유(조회) | WeatherGateway.shortForecast(lat,lng) | RestClient, WeatherProperties |
ExternalContractSupport (test 신규, infra/external test) | 계약 테스트 공통 하네스 — MockWebServer 기동·fixture 로더·live 태그 스킵 판정 | 테스트 fixture 규약 | MockWebServer 세팅 헬퍼, requireLiveKey(envName) | okhttp MockWebServer |
verifyExternalLive gradle task (build.gradle.kts) | live 태그 스모크만 실행하는 opt-in 태스크 | 태스크 정의 | ./gradlew verifyExternalLive | Kotest tag filter |
| env 스위치 config (application.yml + 3 Properties) | mock↔real 전환 스위치 | 설정값 | ConfigurationProperties | env |
서버 토폴로지 판단: 신규 워커·스케줄러·소켓 서버 없음. 단일 API 서버 유지. 근거 — 이 과제는 설정·검증·조사이며 신규 런타임 처리가 없다. 대량 조회 트래픽은 상시 트래픽 시뮬레이터가 계속 mock을 사용(PRD Non-Goals)하므로 실연동 API에 부하 서버가 필요 없다. 실 응답 검증은 test-time(CI fixture) + 개발 머신 1회성 gradle task로 충분.
바운디드 컨텍스트 판단: 신규 도메인 분리하지 않음 → 기존 facility·weather에 합류. 근거 — 이 작업은 독립 라이프사이클·독립 데이터 소유가 없는 “기존 Gateway의 검증·설정”이다. 신규 데이터 소스는 발굴만 하고 구현하지 않으므로(Non-Goals) 도메인/Gateway 신설도 없다. ① TDD 컨텍스트 맵을 변경하지 않는다(facility=코어, weather=지원/generic subdomain 유지).
인터페이스 시그니처 — 계약 테스트 대상 (동결 확인)
계약 테스트는 아래 domain interface 시그니처를 통해 GatewayImpl을 호출한다. 시그니처 변경 없음 — 검증만 추가.
// domain/facility/gateway/GeocodingGateway
fun geocode(address: String): Coordinate? // 실패·미스 시 null
// domain/facility/gateway/PublicSportsFacilityGateway
fun fetchPage(pageNo: Int, numOfRows: Int): List<PublicFacility> // 실패 시 emptyList
// domain/weather/gateway/WeatherGateway
fun shortForecast(lat: Double, lng: Double): Forecast // 실패 시 빈 Forecast (BE-03에서 정합)
신규 test 하네스 시그니처(BE-01 확정):
// test infrastructure/external/ExternalContractSupport
fun startMockServer(): MockWebServer // fixture 응답 enqueue용
fun loadFixture(path: String): String // test/resources/fixtures/external/** 로드
fun requireLiveKey(envName: String): String? // null이면 live 스펙 스킵(태그 기반)
실패 경로·동시성·멱등 (시니어 관점)
| 상황 | 현재 | TO-BE 결정 |
|---|---|---|
| 외부 타임아웃/네트워크 오류 | geocoding→null, facility→emptyList, weather→예외 전파 | weather도 graceful degradation(빈 Forecast)로 정합(BE-03). connect 3s/read 5s 타임아웃은 유지 |
| 5xx / quota 초과(data.go.kr resultCode≠00) | 미검사 (역직렬화 실패 시 빈 결과) | 계약 테스트에 resultCode≠00·빈 items 케이스 추가(BE-02/03) — degrade 동작 검증 |
| 재시도 | 없음(단일 시도) | 추가하지 않음 — 개발용 저빈도 호출이라 재시도 불요(단순함 우선). Observability 대시보드(별도 과제)로 실패율만 관측 |
| 멱등 | 조회 전용 GET | 자연 멱등 — 별도 멱등 키 불요. SMS(POST)는 mock 유지라 무관 |
| 동시성 | 조회 전용, 공유 상태 쓰기 없음 | 락 불요 |
상태 전이 표
이 과제는 도메인 상태 머신을 추가하지 않는다(조회·설정·검증). 유일한 준-상태는 env 스위치 상태다.
| 현재 상태 × 이벤트 | 다음 상태 | 비고 |
|---|---|---|
mock(키 부재) × api-key env 주입 + 재기동 | real(실 API host) | base-url도 실 host로 함께 교체 필요 |
real × api-key env 제거 + 재기동 | mock(localhost:910x) | 롤백 경로 |
| mock/real × 무효 키로 real 전환 | real(degrade) | 401/403 → graceful 빈 결과(geocoding null / facility·weather empty) |
Component Diagram (Mermaid flowchart LR)
flowchart LR subgraph Domain["domain (facility·weather)"] GG["GeocodingGateway"] PF["PublicSportsFacilityGateway"] WG["WeatherGateway"] end subgraph Infra["infrastructure"] KG["KakaoGeocodingGatewayImpl"] DG["DataGoKrPublicFacilityGatewayImpl"] KM["KmaWeatherGatewayImpl"] RF["ExternalRestClientFactory"] end subgraph Ext["외부/mock"] MW["MockWebServer (계약 테스트)"] RA["실 API / mock host"] end KG -.implements.-> GG DG -.implements.-> PF KM -.implements.-> WG KG --> RF DG --> RF KM --> RF RF --> RA RF --> MW
Sequence Diagram — 계약 검증 (CI fixture + live 스모크)
sequenceDiagram participant T as ContractTest participant S as ExternalContractSupport participant M as MockWebServer participant G as GatewayImpl participant R as 실 API T->>S: loadFixture(recorded.json) T->>M: enqueue(fixture) T->>G: geocode/fetchPage/shortForecast G->>M: HTTP GET M-->>G: fixture 응답 G-->>T: 도메인 객체 (스키마 락 검증) Note over T,R: live 태그 스펙 (opt-in) T->>S: requireLiveKey(env) S-->>T: 키 없으면 스킵 T->>G: (실 host) 호출 G->>R: HTTP GET R-->>G: 실 응답 G-->>T: 무손실 역직렬화 확인
ERD
이 과제는 스키마를 변경하지 않는다(설정·검증·조사 전용). 신규 테이블·컬럼 없음 → 해당 없음. env 스위치 상태는 위 “상태 전이 표” 참조.
Testing Plan
계약 검증 테스트가 중심이다. 신규 비즈니스 로직은 weather 실패 경로 정합 1건뿐.
| 레벨 | 대상 | 범위 | 도구 |
|---|---|---|---|
| infrastructure (신규) | 3개 GatewayImpl 계약 검증 | fixture 역직렬화·매핑·HTTP 경로가 mock 스키마와 일치 | Kotest + MockWebServer(okhttp) |
| infrastructure (신규, live 태그) | 3개 GatewayImpl 실 API 스모크 | 키 존재 시 실 응답 무손실 역직렬화 (opt-in verifyExternalLive) | Kotest tag + 실 env |
| infrastructure (신규) | KmaWeatherGatewayImpl 실패 경로 | 5xx/timeout/빈 items → 빈 Forecast degrade | Kotest + MockWebServer |
| — | domain/application/presentation | 신규 로직 없음 → 신규 테스트 없음 | — |
핵심 실패 경로 시나리오(테스트가 잡아야 하는 것):
- 카카오 실 응답
documents[].x/y필드가 mock과 동일 →Coordinate(lat=y, lng=x)정확 매핑.documents빈 배열 →null. - data.go.kr 실 응답
response.body.items.item[]→PublicFacility매핑.resultCode≠00또는 5xx →emptyListdegrade. - 기상청 실 응답 category(TMP/SKY/PTY/POP/REH/WSD) 그룹핑 →
ForecastSlot. 5xx/timeout → 빈Forecast(정합 후). 빈 items → 빈Forecast. live스모크는 키 부재 시 스킵되고 CI를 붉게 만들지 않는다.
Release Scenario — 무중단 배포 (의무)
이 과제의 코드 변경은 3종이며 모두 런타임 하위 호환이다.
- 계약 테스트 추가 — 런타임 무영향(테스트 전용). 단일 PR 배포.
- weather 실패 경로 정합 — happy-path 동작 불변. 실패 시 예외 대신 빈
Forecast반환(더 안전). 하위 호환. - env 스위치 config 정합 — env 우선이므로 프로덕션 값에 영향 없음. weather api-key 기본값을 public-facility와 동일 규약으로 맞추는 것뿐.
env 전환(mock→real) 무중단 절차 — expand-contract:
- expand:
KAKAO_REST_API_KEY·DATA_GO_KR_SERVICE_KEY와 실 base-url env를 배포 환경에 추가(기존 인스턴스는 아직 mock 기본값 → no-op). - switch: 인스턴스를 롤링 재기동해 실 host 반영(ConfigurationProperties는 hot-reload가 아니므로 재기동 필요). 블루그린/롤링으로 무중단. 전환 조건 =
verifyExternalLive가 실 응답 무손실 역직렬화 확인. - contract: 전환 확정 후 mock 서버는 개발·시뮬레이터용으로 계속 유지(제거하지 않음 — 시뮬레이터가 mock 사용).
- 롤백: 실 API 이상 시 실 base-url/api-key env 제거 후 재기동 → mock host 복귀. env 존재 여부가 피처 플래그 등가. weather 실패 경로/계약 테스트 변경은 커밋 revert(런타임 무영향).
Observability (조건부 — PRD Operations)
- 실연동 전환 API의 호출 실패율·레이턴시를 옵저버빌리티 대시보드에 노출(별도 과제
옵저버빌리티 스택 도입과 조율). 이 과제는 지표 소스(gateway 실패 로그 — 이미logger.warn)만 확인하고 대시보드 구성은 위임. - 무료 API 일 한도 80% 도달 알림은 별도 과제(
지능형 장애 알림) 채널과 조율(PRD Operations). 이 과제 범위 밖.
Open Questions
- (PRD Open Q1) 신규 발굴 소스의 실제 기능 개발 여부·편입 과제 → 보류. 발굴표까지가 이 과제. 에어코리아·초단기실황은 weather 도메인 확장 후보로 기록.
- (PRD Open Q2) 전환 임계 1,000건 적정성 → 실 사용량 관측(옵저버빌리티) 후 재조정. 현재 조사 대상 무료 한도(10k~100k)가 임계를 크게 상회해 판정에 영향 없음.
- 녹화 fixture를 실 응답으로 seed할지 mock 응답으로 seed할지 → 실 응답 1회 캡처 권장(키 발급 후). 키 발급 전에는 스키마 동일한 mock 응답으로 seed하고,
verifyExternalLive최초 실행 시 실 응답과 대조해 승격.
Document History
| 날짜 | 변경 내용 |
|---|---|
| 2026-07-03 | 최초 작성 — 실측 AS-IS(env 스위치 4블록·키 공유·weather 실패 경로 불일치·검증 부재), 발급 주체 조사표, 전환 판정(1,000건 임계), MockWebServer fixture 계약 검증 + live 태그 설계, mock 유지 분류, 신규 소스 3+ 발굴, 무중단 전환 절차 |