DevJong12

[Picasso][Spring] @Scheduled를 통한 경매 상태 변경 본문

프로젝트/Picasso

[Picasso][Spring] @Scheduled를 통한 경매 상태 변경

Jong12 2023. 10. 2. 23:20
728x90

목차

    개요

    💡해당 게시물은 Scheduled API를 뜯어보는 게시물이 아니다. 필자가 필요한 만큼만 활용을 하였다.
    프로젝트를 제작함에 있어서, 올라온 게시물이 경매시작일인 경우 경매를 시작하거나 유찰이 되도록 상태값을 변경하고, 사용자한테 메일을 발송해야 하는 로직이 존재하였다.

    상태를 변경함에 있어서 Database의 Event Scheduler를 활용하는 것도 방법이 될 수 있었다. 
    하지만 그건 결코 좋은 방법이 아니라고 필자는 생각했고, 첫번째로는 그래봐야 메일도 제시간에 보내야 하는것이었고, 두번째로는 해당이유가 대부분의 이유를 차지하는데 Database에 의존을 해야 하게 되는 문제가 발생하기 떄문이다.

    Database에 의존을 해버리게 되면 추후 Database를 MySQL에서 Oracle등으로의 이동이 힘들어 지는 것과, HDD의 영역의 Database보단 Memory레벨로 데이터를 가져와서 작업하는게 더 빠를 것이라는 CS적인 지식에서의 문제로 인하여 Scheduler를 통해서 Java기능으로 제작을 해보았다.

    기능 정의

    • 9시에 경매시작일인 미술품의 경매가 시작되야한다.
      • 만약 관리자 승인이 이뤄진 경매품의 경우 경매가 시작되는 상태값과, 경매시작 안내 메일을 작성자한테 발송한다.
      • 관리자의 승인이 이뤄지지 않은 경매품의 경우에는 경매가 유찰되었다는 상태값과 함께 경매 유찰안내 메일을 작성자한테 발송한다.
    • 18시에는 경매 종료일인 미술품의 경매가 종료되어야 한다.
      • 만약 입찰자가 존재하지 않는 경매품의 경우에는 경매를 유찰상태로 변경하면서, 미술품이 유찰되었다는 안내메일을 작성자한테 발송한다.
      • 만약 입찰자가 존재하는 경우에는 경매품의 상태값을 낙찰로 변경하면서 마지막 낙찰자와 미술품의 경매의 낙찰안내메일을 작성자한테 발송한다.

    사전 필요 설정

    @SpringBootApplication에서 @EnableScheduling의 선언이 필요하며 Configuration으로 따로 분리하여 선언해도 상관은 없을 것 같다.
    @EnableScheduling // Schedule사용을 위한 Annotation
    @SpringBootApplication
    @RequiredArgsConstructor
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }

    경매의 시작 및 종료 코드

    시작 스케쥴링 코드

    상태값이 두개를 분리하기 편한 상태다. 관리자의 승인전, 승인후가 명확히 구분을 해놨었기 때문에 그냥 메소드 두개로 분류를 하였다.
    한개의 기능에 한개의 책임만 주고 싶었기 떄문이다.

    참고로 코드에서 나오는 DateStaticConstants.ZONE_SEOUL은 필자가 만든 Static변수로 Asia/Seoul을 의미한다
    더보기
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    import picasso.server.api.auction.service.PictureService;
    import picasso.server.api.mail.service.SendMailService;
    import picasso.server.common.util.DateStaticConstants;
    
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    
    import static picasso.server.domain.domains.picture.items.PictureStatus.AFTER_APPROVE;
    import static picasso.server.domain.domains.picture.items.PictureStatus.BEFORE_APPROVE;
    import static picasso.server.domain.domains.picture.items.PictureStatus.BIDDING;
    
    /**
     * 9시에 경매 전 상태인 게시물들을 처리하는 스케쥴러
     */
    
    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class StartAuctionScheduler {
        private final PictureService pictureService;
        private final SendMailService sendMailService;
    
        @Transactional
        @Scheduled(cron = "0 0 9 * * *", zone = DateStaticConstants.ZONE_SEOUL)
        public void startApprovePictureToBiddingAuction() {
            log.info("Start Todays Auctions Open Schedule Runtime : NowTime >>> {}", LocalDateTime.now());
            pictureService
                    .changePictureStatusByPictureStatusAndBidStartDate(AFTER_APPROVE, BIDDING, LocalDate.now())
                    .forEach(sendMailService::startBiddingMail);
            log.info("End Todays Auctions Open Schedule Runtime : NowTime >>> {}", LocalDateTime.now());
        }
    
    
        @Transactional
        @Scheduled(cron = "0 0 9 * * *", zone = DateStaticConstants.ZONE_SEOUL)
        public void startNotApprovePictureToRejectAuction() {
            log.info("Start Reject Picture Schedule Runtime : NowTime >>> {}", LocalDateTime.now());
            pictureService
                    .changePictureStatusByPictureStatusAndBidStartDate(BEFORE_APPROVE, BIDDING, LocalDate.now())
                    .forEach(sendMailService::pictureRejectMailWithNotApproveAdmin);
            log.info("End Todays Auctions Open Schedule Runtime : NowTime >>> {}", LocalDateTime.now());
        }
    }

     

    경매 종료 스케쥴링 코드

    해당 기능이 정말 힘들었었다. 어떻게든 Transactional을 유지해 와야하고, History존재유무로 유찰인지 낙찰인지를 판단하고, 메일을 보내는데 내가 내 머리안에서도 로직이 꼬인다는 느낌을 많이 받은 기능중에 하나였다.
    더보기
    import lombok.RequiredArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.annotation.Transactional;
    import picasso.server.api.auction.service.PictureService;
    import picasso.server.api.mail.service.SendMailService;
    import picasso.server.common.util.DateStaticConstants;
    import picasso.server.domain.domains.picture.items.Picture;
    
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.util.List;
    
    import static picasso.server.domain.domains.picture.items.PictureStatus.BIDDING;
    import static picasso.server.domain.domains.picture.items.PictureStatus.REJECT;
    import static picasso.server.domain.domains.picture.items.PictureStatus.SUCCESS_BID;
    
    /**
     * 18시에 경매 전 상태인 게시물들을 처리하는 스케쥴러
     */
    
    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class EndAuctionScheduler {
        private final PictureService pictureService;
        private final SendMailService sendMailService;
    
        @Transactional
        @Scheduled(cron = "0 0 18 * * *", zone = DateStaticConstants.ZONE_SEOUL)
        public void startAuction() {
            log.info("Today End Auctions Schedule Start Time  >>> {}", LocalDateTime.now());
            List<Picture> todayEndPictursList = pictureService.findPictureStatusByStatusAndBidEndDate(BIDDING, LocalDate.now());
            todayEndPictursList.forEach(
                    picture -> {
                        if (picture.getBidHistory().isEmpty()) {
                            picture.setPictureStatus(REJECT);
                            sendMailService.pictureRejectMailWithFinishDate(picture);
                        } else {
                            picture.setPictureStatus(SUCCESS_BID);
                            sendMailService.pictureSuccessBidMail(picture);
                        }
                    }
            );
            log.info("Today End Auctions Schedule End Time  >>> {}", LocalDateTime.now());
        }
    
    }

    제작을 해보면서..

    스케쥴링을 언제 한번 해보고 싶다고 많이 생각은 해봤는데... 이렇게 해보게 될 줄은 몰랐다.
    스케쥴링 자체는 문제가 없는데, 이걸 몇초단위 혹은 몇시에 실행할지 등을 생각해보고 오래걸리는 API와 연결을 하면서 스케쥴링에서 시간이 너무 지연되는 문제들 등이 존재하였다.

    하지만 제작을 해보면서 나는 정말 유용하게 써먹어 볼 수 있는 기능이라는게 한줄한줄 작성하면서 몸소 체감할 수 있는 기능이었다는게 느껴지는게 정말 오래간만에 진짜 재밌게 개발이란걸 체험해 볼 수 있던 시간이었지 않나 싶다.
    728x90
    Comments