개발로 사고하기

Spring Boot + Redis로 아파트 실거래 조회 API 만들기

tues 2025. 10. 1. 18:56

 

최근에 공공데이터포털 아파트 실거래 조회 API를 이용해서 프론트엔드에서 거래 내역을 보여주는 프로젝트를 진행했다.
하지만 바로 부딪힌 문제는 속도와 중복 호출이었다.

  1. 응답 속도
    • 정부 API 특성상 XML로 데이터를 내려주고, 이를 JSON으로 변환해야 했다.
    • 네트워크도 느리고 응답 처리 과정까지 거치니 사용자가 같은 조건으로 여러 번 조회할 때마다 매번 느린 응답을 받아야 했다.
  2. 중복 호출
    • 유저가 같은 조건(예: "강남구 2025년 9월")으로 여러 번 검색할 경우,
      매번 정부 서버를 다시 치게 되어 불필요한 트래픽이 발생했다.
  3. 최근 검색 내역 없음
    • "내가 조금 전에 검색했던 조건"을 다시 보려면,
      유저가 직접 다시 입력해야 했다.

👉 이 세 가지 문제를 해결하기 위해 Spring Boot + Redis 캐시를 도입하기로 했다.

  • 캐시(Cache): 같은 조건으로 조회하면 일정 시간 동안은 Redis에서 바로 꺼내오기 → 빠른 응답
  • 최근 검색(Recent List): 사용자가 조회한 조건을 Redis 리스트에 저장해 두고, 최근 검색 내역을 보여주기

전체적인 구조

[Frontend (Vite/React)]
         │
         ▼
[Spring Boot (8081)]
  - Redis Cache (@Cacheable)
  - Redis List (최근 검색)
         │
         ▼
[Node Proxy (8080, Express)]
  - 공공데이터포털 API 호출
  - XML → JSON 변환
         │
         ▼
[Government Open API]
         │
         ▼
[Redis (6379)]
  - 캐시 저장소
  - 최근 검색 내역 기록

 


동작 원리

  1. 프론트에서 /api/trades 호출
  2. Spring Boot가 먼저 Redis 캐시 확인
    • 캐시 HIT → 바로 응답
    • 캐시 MISS → Node 프록시 호출
  3. Node 프록시가 정부 API 호출 후 JSON 응답으로 정규화
  4. Spring Boot가 받은 결과를 Redis에 저장
    • 캐시: TTL 2분
    • 최근 검색: Redis List로 기록 (최대 20개, 7일 TTL)
  5. 프론트에서 /api/recent 호출 시 Redis에 쌓인 최근 검색 내역 응답

아키텍처 다이어그램

┌───────────────────────────────┐
│         Frontend (5173)       │
│   React / Vite / TS           │
└───────────────▲───────────────┘
                │ fetch /api/*
                │
┌───────────────┴───────────────┐
│     Spring Boot (8081)        │
│  - @Cacheable (Redis Cache)   │
│  - 최근검색 (Redis List)       │
└───────────────▲───────────────┘
                │ REST call
┌───────────────┴───────────────┐
│   Node Proxy (8080, Express)  │
│   - 공공데이터포털 API 호출    │
│   - XML → JSON 변환           │
└───────────────▲───────────────┘
                │
┌───────────────┴───────────────┐
│ Government Open API (RTMS)    │
└───────────────────────────────┘

┌───────────────────────────────┐
│          Redis (6379)          │
│  - Cache (2분 TTL)             │
│  - 최근검색 리스트(20개, 7일)   │
└───────────────────────────────┘

 


컨트롤러

package com.example.redis_demo.api;

import com.example.redis_demo.dto.TradeListResp;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
public class DealController {

    private final DealService service;

    @GetMapping("/trades")
    public TradeListResp trades(
            @RequestParam String sgg,
            @RequestParam String ym,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestHeader(value = "X-User-Id", required = false) String userId
    ) {
        return service.getDeals(sgg, ym, page, size, userId);
    }

    @GetMapping("/recent")
    public List<String> recent(
            @RequestParam(defaultValue = "10") int limit,
            @RequestHeader(value = "X-User-Id", required = false) String userId
    ) {
        return service.getRecent(userId, limit);
    }
}