외부 API 연동

외부 API 연동 문서

청약플래닛 백엔드 관련 문서입니다.

외부 API 통합 문서

🌐 개요

청약플래닛은 종합적인 부동산 정보를 제공하기 위해 여러 한국 정부 및 상업 API와 통합됩니다. 이 문서는 모든 외부 API 통합, 목적 및 구현 세부 정보를 설명합니다.

🏛️ 정부 API

1. 한국토지주택공사(LH) API

목적

정부 주택 공사로부터 한국 공공 주택 청약 정보를 직접 검색합니다.

API 세부 정보
  • 제공자: 한국토지주택공사(LH)
  • 기본 URL: https://api.odcloud.kr/api/ApplyhomeInfoDetailSvc/v1
  • 인증: 서비스 키 (API 키)
  • 호출 제한: 1일 1000건
  • 데이터 형식: JSON
사용된 엔드포인트
아파트 청약 세부 정보 가져오기
GET /getAPTLttotPblancDetail
매개변수
  • serviceKey: API 인증 키
  • page: 페이지 번호 (기본값: 1)
  • perPage: 페이지당 레코드 수 (기본값: 50, 최대: 100)
  • HOUSE_MANAGE_NO: 주택 관리 번호 (선택 사항)
  • PBLANC_NO: 공고 번호 (선택 사항)
응답 예시
{
  "currentCount": 1,
  "data": [
    {
      "HOUSE_MANAGE_NO": "2024000001",
      "PBLANC_NO": "2024강남01",
      "HOUSE_NM": "래미안 강남포레스트",
      "HSSPLY_ADRES": "서울특별시 강남구 대치동",
      "TOT_SUPLY_HSHLDCO": "100",
      "RCRIT_PBLANC_DE": "2024-06-19",
      "RCEPT_BGNDE": "2024-07-01",
      "RCEPT_ENDDE": "2024-07-03",
      "SPSPLY_RCEPT_BGNDE": "2024-06-25",
      "SPSPLY_RCEPT_ENDDE": "2024-06-27"
    }
  ],
  "matchCount": 1,
  "page": 1,
  "perPage": 50,
  "totalCount": 1
}
구현 세부 정보

서비스 통합:

@Service
public class SubscriptionService {

  @Value("${sub.apt.api.url}")
  private String subAptApiUrl;

  @Value("${sub.apt.api.key}")
  private String apiKey;

  public String updateSubAPT() {
    String requestUrl = subAptApiUrl + "?page=1&perPage=50&serviceKey=" + apiKey;
    try {
      ResponseEntity response = restTemplate.exchange(
        requestUrl, HttpMethod.GET, null, String.class);
      processSubscriptionData(response.getBody());
    } catch (Exception e) {
      log.error("LH API 호출 실패: {}", e.getMessage());
      return "API 호출 실패: " + e.getMessage();
    }
    return "Success";
  }
}
오류 처리
  • 네트워크 시간 초과: 지수 백오프를 사용하여 재시도
  • 호출 제한: 사용량 추적을 통해 일일 제한 준수
  • 잘못된 응답: 문제성 레코드 로깅 및 건너뛰기
  • 인증 실패: 관리자에게 알림

2. 국토교통부 API

목적

시장 분석 및 가격 비교를 위한 부동산 거래 가격 데이터 검색

API 세부 정보
  • 제공자: 국토교통부
  • 기본 URL: https://apis.data.go.kr/1613000/RTMSDataSvcAptTrade
  • 인증: 서비스 키
  • 호출 제한: 1일 1000건
  • 데이터 형식: XML/JSON
사용된 엔드포인트
아파트 거래 데이터 가져오기
GET /getRTMSDataSvcAptTrade
매개변수
  • serviceKey: API 인증 키
  • LAWD_CD: 행정 구역 코드 (시군구 코드)
  • DEAL_YMD: 거래 연월 (YYYYMM)
  • numOfRows: 레코드 수 (기본값: 50)
  • pageNo: 페이지 번호
응답 예시

  
    
      
        <거래금액>120,000
        <건축년도>2020
        <년>2024
        <법정동>대치동
        <아파트>래미안
        <월>06
        <일>15
        <전용면적>84.93
        <지번>123-1
        <층>15
      
    
  
구현 세부 정보

비동기 처리:

@Async
@Retryable(value = Exception.class, maxAttempts = 3)
public CompletableFuture collectRealPriceData(String dealYMD) {
  List codes = sggCodeRepository.findAll();
  List> futures = codes.stream()
    .map(code -> CompletableFuture.runAsync(() ->
      processCodeData(code, dealYMD), taskExecutor))
    .collect(Collectors.toList());
  return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
}

🗺️ 상업 API

3. 카카오 지도 API

목적

한국 주소를 지리적 좌표로 변환하고 위치 기반 서비스를 제공합니다.

API 세부 정보
  • 제공자: 카카오 주식회사
  • 기본 URL: https://dapi.kakao.com
  • 인증: REST API 키
  • 호출 제한: 1일 300,000건
  • 데이터 형식: JSON
사용된 엔드포인트
주소 검색 (지오코딩)
GET /v2/local/search/address.json
Authorization: KakaoAK {REST_API_KEY}
매개변수
  • query: 검색할 주소 (한국어)
  • analyze_type: 분석 유형 (similar, exact)
  • page: 페이지 번호
  • size: 페이지당 결과 수 (1-30)
응답 예시
{
  "meta": {
    "total_count": 1,
    "pageable_count": 1,
    "is_end": true
  },
  "documents": [
    {
      "address_name": "서울 강남구 대치동 944-32",
      "y": "37.494870",
      "x": "127.062583",
      "address_type": "REGION_ADDR",
      "address": {
        "region_1depth_name": "서울",
        "region_2depth_name": "강남구",
        "region_3depth_name": "대치동",
        "mountain_yn": "N",
        "main_address_no": "944",
        "sub_address_no": "32"
      }
    }
  ]
}
구현 세부 정보

WebClient 설정:

@Configuration
public class WebClientConfig {

  @Value("${kakao.api.key}")
  private String kakaoApiKey;

  @Bean
  public WebClient kakaoWebClient() {
    return WebClient.builder()
      .baseUrl("https://dapi.kakao.com")
      .defaultHeader("Authorization", "KakaoAK " + kakaoApiKey)
      .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(1024 * 1024))
      .build();
  }
}

주소 지오코딩 서비스:

@Service
public class GeocodingService {

  private final WebClient webClient;

  public GeocodingService(WebClient kakaoWebClient) {
    this.webClient = kakaoWebClient;
  }

  public Mono getCoordinates(String address) {
    return webClient.get()
      .uri(uriBuilder -> uriBuilder
        .path("/v2/local/search/address.json")
        .queryParam("query", address)
        .build())
      .retrieve()
      .bodyToMono(String.class)
      .map(this::parseCoordinates)
      .onErrorReturn(new CoordinateResponseDTO(null, null));
  }

  private CoordinateResponseDTO parseCoordinates(String jsonResponse) {
    try {
      JsonNode root = new ObjectMapper().readTree(jsonResponse);
      JsonNode document = root.path("documents").get(0);
      if (document != null) {
        double latitude = document.path("y").asDouble();
        double longitude = document.path("x").asDouble();
        return new CoordinateResponseDTO(latitude, longitude);
      }
    } catch (Exception e) {
      log.error("좌표 파싱 실패: {}", e.getMessage());
    }
    return new CoordinateResponseDTO(null, null);
  }
}

🔧 통합 패턴

1. 재시도 메커니즘

@Retryable(
  value = {RestClientException.class, SocketTimeoutException.class},
  maxAttempts = 3,
  backoff = @Backoff(delay = 1000, multiplier = 2)
)
public String callExternalAPI(String url) {
  return restTemplate.getForObject(url, String.class);
}

2. 회로 차단기

@Component
public class ExternalAPICircuitBreaker {

  private final RestTemplate restTemplate;
  private final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("externalAPI");

  public String callWithCircuitBreaker(String url) {
    return circuitBreaker.executeSupplier(() -> restTemplate.getForObject(url, String.class));
  }
}

3. 호출 제한

@Component
public class RateLimitedAPIClient {

  private final RestTemplate restTemplate;
  private final RateLimiter rateLimiter = RateLimiter.create(1.0);

  public String callAPI(String url) {
    rateLimiter.acquire();
    return restTemplate.getForObject(url, String.class);
  }
}

4. 캐싱 전략

@Cacheable(value = "externalAPICache", key = "#url")
public String getCachedAPIResponse(String url) {
  return callExternalAPI(url);
}

@CacheEvict(value = "externalAPICache", allEntries = true)
@Scheduled(fixedRate = 3600000)
public void clearCache() {
  log.info("외부 API 캐시 지우는 중");
}

📊 모니터링 및 알림

API 상태 확인

@Component
public class APIHealthIndicator implements HealthIndicator {

  private final SubscriptionService subscriptionService;
  private final GeocodingService geocodingService;
  private final NaverNewsAPI naverNewsAPI;

  @Override
  public Health health() {
    try {
      testLHAPI();
      testKakaoAPI();
      testNaverAPI();
      return Health.up().withDetail("apis", "모든 외부 API가 정상입니다.").build();
    } catch (Exception e) {
      return Health.down().withDetail("error", e.getMessage()).build();
    }
  }

  private void testLHAPI() throws Exception { subscriptionService.updateSubAPT(); }
  private void testKakaoAPI() throws Exception { geocodingService.getCoordinates("서울시 강남구").block(); }
  private void testNaverAPI() throws Exception { naverNewsAPI.fetchNews("부동산", 1, 1); }
}

외부 API 문서 버전: 1.0

최종 업데이트: 2025-06-26

총 통합: 5개 API