티스토리 뷰

프로젝트에서 정기 결제 스케줄을 만들기로 결정했습니다.

사용자가 주기를 설정하면,

예를 들어 30일 주기로 배송을 정하면 30일마다 결제가 되게 해야 했습니다(프로젝트에서 결제가 되면 배송이 된다고 가정).

그리고 사용자는 배송 주기를 변경할 수 있고, 취소도 할 수 있습니다.

처음에는 어떻게 구현해야 할지 막막했지만 자바로 구현할 수 있는 스케쥴러를 찾아보다 Spring에서 제공하는 Scheduling Tasks를 사용하게 되었습니다. 

https://spring.io/guides/gs/scheduling-tasks/

 

Getting Started | Scheduling Tasks

Although scheduled tasks can be embedded in web applications, the simpler approach (shown in this guide) creates a standalone application. To do so, package everything in a single, executable JAR file, driven by a Java main() method. The following snippet

spring.io

그리고 후에 기술적인 한계로 인해 Quartz 사용하여 구현하게 되었습니다. 

http://www.quartz-scheduler.org/

 

Quartz Enterprise Job Scheduler

What is the Quartz Job Scheduling Library? Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application - from the smallest stand-alone application to the largest e-commerce system. Quartz can

www.quartz-scheduler.org

 

Spring Batch까지 사용했으면 좋았겠지만 시간과 능력(?) 문제로 Batch 스케쥴링 까지는 구현하지 못했습니다.

이 포스트는 처음에 스프링 스케쥴러를 사용하여 정기 결제를 구현한 내용을 담았습니다.
스케쥴러에 초점을 맞췄기 때문에 결제 관련 코드는 없습니다!

먼저 프로젝트에서는 SpringBoot 2.7.5 버전을 사용했고 java 11입니다!


1. 스프링 스케줄 사용

1.1 스케줄러 설정

스프링 스케줄러의 설정입니다.

@Configuration
public class ScheduleConfig {

    @Bean
    public ThreadPoolTaskScheduler schedulerExecutor() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(4); // 스레드 풀 사이즈
		// 수행할 수 없는 task가 생길 경우 RejectedExecutionException을 던진다.
        taskScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
			
        return taskScheduler;

}

 

1.2 스케줄링 구현

먼저 설정한 스케줄러 DI 하여 사용합니다. 

private final ThreadPoolTaskScheduler scheduler; //위에서 설정한 스케쥴러

 

scheduler에서 schedule() 메서드를 수행하면 스케줄링이 됩니다.

아래 코드는 자바에서 제공하는 코드입니다.

Runnable 타입과 Trigger 타입이 파라미터로 들어갑니다.

public ScheduledFuture<?> schedule(Runnable task, Trigger trigger){
    ScheduledExecutorService executor= getScheduledExecutor();
    //생략
}

 

특히, 스케쥴링이 수행하는 job은 Runnable 타입으로 만들어야 합니다. 

   public Runnable autoPay(ItemOrder itemOrder) {
            return () -> {
                try {
                    service.pay(itemOrder);
                } catch (IOException e) {
                    throw new BusinessLogicException(ExceptionCode.ORDER_NOT_FOUND);
                }
            };
   }

 

Trigger 타입은 PeriodicTrigger라는 객체를 사용하여 주기를 설정했습니다.

또한, 나중에 스케줄을 조회하기 위해 Map을 사용하여 따로 스케줄을 저장했습니다.

private final ConcurrentMap<Long, ScheduledFuture<?>> scheduledFutureMap = new ConcurrentHashMap<>();
    ...

public void startScheduler(Long orderId,ItemOrder itemOrder){
    trigger =new PeriodicTrigger(itemOrder.getPeriod(), TimeUnit.SECONDS); //예시 코드 초 단위로 설정
    
    ScheduledFuture<?> schedule=scheduler.schedule(autoPay(itemOrder),trigger);
    scheduledFutureMap.put(orderId+itemOrder.getItemOrderId(), schedule);
}

 

1.3 스케줄 취소

스케줄을 취소할 경우 스케줄을 저장한 저장소에서 그 스케줄을 조회한 후 cancel(true) 메서드를 사용해서 취소할 수 있습니다.

public void stopScheduler(Long orderId,Long itemOrderId){
    ScheduledFuture<?> scheduledFuture=scheduledFutureMap.get(orderId+itemOrderId);
    scheduledFuture.cancel(true);
}

 

1.4 스케줄 주기 변경

주기를 변경해야 할 경우

  • 스케줄을 조회한다.
  • 조회한 스케줄을 취소한다.
  • 스케줄을 null로 초기화한다.
  • 새로운 주기 객체를 생성한 후 다시 스케줄을 만들어 실행시킨다.

이 로직을 따릅니다.

public void changePeriod(Long orderId, ItemOrder itemOrder, Integer period) {

    itemOrder.setPeriod(period);
	
    //스케쥴을 가지고 온다.
    ScheduledFuture<?> scheduledFuture = scheduledFutureMap.get(orderId + itemOrder.getItemOrderId()); 

    if (scheduledFuture != null)
        scheduledFuture.cancel(true); //스케쥴을 취소한다.
    	
    
    scheduledFuture = null; //null로 만든다.
    
	
    // 스케쥴을 재설정한다.
    trigger = new PeriodicTrigger(period, TimeUnit.SECONDS); 
    scheduledFuture = scheduler.schedule(autoPay(itemOrder), trigger);
    scheduledFutureMap.put(orderId + itemOrder.getItemOrderId(), scheduledFuture);
}

 


스프링 스케줄러를 사용했을 경우의 한계

스프링 스케쥴러는 만든 정기 결제의 비즈니스 로직 상의 문제가 있었습니다.

처음 정기 결제를 신청하고 주기를 바꿨을 때 처음 결제일을 기준으로 주기가 변경돼야 했습니다.

하지만 스케줄이 시작되고 주기를 바꿀 때 스케쥴러는 그 스케줄을 삭제하고 다시 생성하는 구조이기 때문에 변경 시점이 처음 결제일이 아닌 주기를 변경한 시점을 기준으로 주기를 변경합니다.

즉, 스프링 스케쥴러는 런 타임 환경에서 동적으로 주기를 변경하기에는 약간의 한계가 있었습니다.

또한, 스케쥴을 영속적으로 저장하기 위해 데이터베이스를 사용해야 하는데, Scheduler 객체 자체를 데이터베이스에 저장하기에는 한계가 있었습니다.

메모리 영역에 스케쥴을 저장하는 것은 서버에 오류가 생겼을 경우 큰 문제가 생길 것 이라고 판단했습니다.

 

 

 

그래서 Quartz 스케쥴러로 스케쥴링을 다시 구현하게 됐습니다.
https://choizz.tistory.com/30

 

정기결제 구현을 위한 Spring Scheduled에서 Quartz로 전환

앞선 포스트에서는  정기 결제를 구현할 때 스프링에서 제공하는 스케쥴러를 사용했습니다.하지만 정기 결제 로직을 구현하는 데 있어서 런타임에 동적으로 스케쥴러의 주기를 바꿀 수 없는

choizz.tistory.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함