개발 공부/프로젝트

[사이드 프로젝트] 가상 주식 거래소 만들기 (5) - 스케줄러를 이용한 데이터 자동화와 REST API 구현

baby-t 2025. 12. 2. 16:09

1. 들어가며: 서버가 스스로 일하게 만들자

지난 포스팅에서 WebClient를 이용해 업비트 API로부터 데이터를 가져오는 데 성공했습니다. 하지만 치명적인 단점이 있었습니다. **"개발자가 테스트 코드를 실행시켜야만 데이터가 갱신된다"**는 점입니다.

이번 포스팅에서는 서버가 1분마다 자동으로 시세를 갱신하도록 **스케줄러(Scheduler)**를 적용하고, 수집된 데이터를 프론트엔드나 외부에서 조회할 수 있도록 **REST API(Controller)**를 구현하는 과정을 정리합니다. 이로써 백엔드의 '기초 공사'인 데이터 파이프라인(Data Pipeline)이 완성됩니다.


2. 스케줄러(Scheduler) 구현: 숨 불어넣기

스프링 부트는 별도의 복잡한 설정 없이 어노테이션 몇 개만으로 강력한 스케줄링 기능을 제공합니다.

2-1. 스케줄링 활성화 (@EnableScheduling)

가장 먼저 메인 애플리케이션 클래스에 스케줄링 기능을 켜는 스위치를 달아줘야 합니다.

Java
 
@EnableScheduling // (1) 스케줄링 기능 활성화
@SpringBootApplication
public class VirtualExchangeApplication {
    public static void main(String[] args) {
        SpringApplication.run(VirtualExchangeApplication.class, args);
    }
}

2-2. 스케줄러 구현 (StockScheduler)

이제 "1분마다 StockService를 실행해라"라는 명령을 내릴 스케줄러를 만듭니다.

Java
 
@Component // 스프링 빈 등록
@RequiredArgsConstructor
public class StockScheduler {

    private final StockService stockService;

    // (2) 1분마다 실행 (매분 0초에 실행)
    @Scheduled(cron = "0 * * * * *") 
    public void updateStockPrices() {
        System.out.println("⏰ [스케줄러 실행] 1분마다 코인 시세를 갱신합니다.");
        stockService.getStockPrice(); // WebClient 호출 -> DB 저장 (Upsert)
    }
}

✅ 결과 확인 서버를 실행해두고 콘솔을 지켜보면, 정확히 매분 00초마다 로그가 찍히며 DB 데이터가 최신 가격으로 업데이트되는 것을 확인할 수 있습니다. 이제 서버는 24시간 살아 움직이게 되었습니다.


3. API 구현 (Controller): 데이터 개방하기

DB에 데이터가 쌓이고 있지만, 아직은 DB 관리자만 볼 수 있습니다. 웹 화면(Frontend)이나 앱에서 이 데이터를 쓸 수 있게 **창구(API)**를 열어줘야 합니다.

3-1. Service 계층 조회 메서드 추가

Java
 
// StockService.java
public List<Stock> getStocks() {
    return stockRepository.findAll(); // 모든 주식 목록 반환
}

3-2. Controller 구현 (StockController)

Java
 
@RestController // (Q1) JSON 데이터를 반환하는 컨트롤러
@RequiredArgsConstructor
@RequestMapping("/api/stocks") // (Q2) 공통 URL 설정
public class StockController {

    private final StockService stockService;

    @GetMapping // GET 요청 처리
    public List<Stock> getStocks() {
        return stockService.getStocks();
    }
}

✅ 결과 확인 브라우저 주소창에 http://localhost:8080/api/stocks를 입력하면, DB에 저장된 주식 목록이 JSON 배열 형태([{"code": "KRW-BTC", ...}])로 출력되는 것을 확인했습니다.


4. 기술적 회고 (Deep Dive) 💡

단순 구현을 넘어, API를 설계하며 했던 고민들과 학습한 내용을 정리합니다.

Q1. @Controller와 @RestController의 차이는?

처음엔 습관적으로 @Controller를 쓰려다 @RestController를 선택했습니다. 두 어노테이션의 결정적 차이는 **"반환하는 것이 무엇인가"**입니다.

  • @Controller: 주로 **화면(HTML 파일)**을 반환할 때 사용합니다. (ViewResolver가 동작)
  • @RestController: **데이터(JSON)**를 반환할 때 사용합니다. (@Controller + @ResponseBody의 결합)

이번 프로젝트는 프론트엔드에 순수한 데이터(주식 목록)를 전달해야 하므로, 객체를 자동으로 JSON으로 변환해 주는 @RestController가 적합했습니다.

Q2. 같은 URL(/api/stocks)을 써도 되는가? (HTTP Method)

API를 설계하다 보니 "조회할 때도 /api/stocks, 나중에 생성할 때도 /api/stocks를 쓰면 충돌하지 않을까?" 하는 의문이 들었습니다.

결론은 **"충돌하지 않으며, 오히려 권장되는 방식(RESTful)"**입니다. 웹 통신에서 URL은 **'자원(Resource)'**을, HTTP Method는 **'행동(Verb)'**을 의미하기 때문입니다.

  • GET /api/stocks: 주식 목록을 조회해줘.
  • POST /api/stocks: 주식을 생성해줘.

서버는 URL뿐만 아니라 **요청 방식(Header의 Method)**을 보고 서로 다른 메서드(@GetMapping, @PostMapping)로 라우팅하므로, 같은 주소를 사용하는 것이 자원 중심의 설계를 하는 REST API의 정석임을 알게 되었습니다.


5. 마치며

이제 **[데이터 수집 -> DB 저장 -> 스케줄러 자동화 -> API 송출]**로 이어지는 백엔드의 핵심 데이터 파이프라인이 완성되었습니다.

다음 포스팅부터는 백엔드의 영역을 넘어, 사용자에게 실제 거래소 같은 화면을 보여주기 위해 Thymeleaf를 활용한 프론트엔드 구현을 시작해 보겠습니다.