@Async 이용한 이메일 전송비동기 처리

최성욱·2025년 2월 21일
0

이메일 전송 로직을 동기식으로 했을때 문제점

다음은 email을 전송하는 클래스인 EmailProvider의 email전송 메소드이다

public boolean sendCertificationMail(String email,String certificationNumber){

        try {
            MimeMessage message = javaMailSender.createMimeMessage();
            MimeMessageHelper messageHelper = new MimeMessageHelper(message, true);
            String htmlContent = getCertificationMessage(certificationNumber);
            messageHelper.setTo(email);
            messageHelper.setSubject(SUBJECT);
            messageHelper.setText(htmlContent,true);
            javaMailSender.send(message);
        } catch (MessagingException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

다음은 UserService에서 동기식으로 하였을 떄의 로직이다

public EmailCertificationResponse emailCertification(EmailCertificationRequest emailCertificationRequest) {

            List<User> users = userRepository.findByEmail(emailCertificationRequest.getEmail());
            if(users.isEmpty()) throw new NotFoundEmailExeption("Not Found Email");
            if(users.size()>1) throw new DuplicateEmailExeption("Duplicate Email");
            emailProvider.sendCertificationMail(emailCertificationRequest.getEmail(), emailCertificationRequest.getEmail());
            return EmailCertificationResponse.builder()
                        .code(SUCCESS_CODE)
                        .message(SUCCESS_MESSAGE)
                        .build();
    }

이렇게 비즈니스 로직을 구현하게 되면 메소드 안에서의 스레드의 기본 동작은 동기식이다. 따라서 SMTP 서버를 이용해서 이메일을 전송한 후에 응답이 제대로 왔는지 확인한 후에 해당 Json을 반환하기 때문에 속도의 저하가 발생할 수 밖에 없다
-> 그래서 Spring이 기본적으로 제공하는 @Async 어노테이션을 이용하여 별도의 스레드에서 이메일 전송을 비동기로 처리할수 있다

다음은 비동기로 처리하기 위해 @EnableAsync를 붙히고 해당 스레드를 얼마나 사용할지 스레드를 설정하는 부분이다

@EnableAsync
@Configuration
public class AsyncConfig {
    @Bean(name = "customAsyncExecutor")
    public Executor customAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(30);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("CustomExecutor");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}

Async로직은 다른 로직과 달라서 클래스에 작성하였다

@Service
@RequiredArgsConstructor
public class AsyncService {

    private final EmailProvider emailProvider;

    @Async("customAsyncExecutor")
    public void asyncTask(EmailCertificationRequest emailCertificationRequest){
        emailCertification(emailCertificationRequest);
    }

    private void emailCertification(EmailCertificationRequest emailCertificationRequest) {
        emailProvider.sendCertificationMail(emailCertificationRequest.getEmail(), certificationNumber());
    }

    private String certificationNumber(){
        int number = (int) (Math.random() * 9000) + 1000;
        return String.valueOf(number);
    }
}

AsyncService의 메소드에 AsyncConfig에 해당하는 Bean이름을 붙혀주면 설정한 쓰레드풀에서 비동기로 처리하게된다

다음은 UserService를 수정한 부분이다

public EmailCertificationResponse emailCertification(EmailCertificationRequest emailCertificationRequest) {

            List<User> users = userRepository.findByEmail(emailCertificationRequest.getEmail());
            if(users.isEmpty()) throw new NotFoundEmailExeption("Not Found Email");
            if(users.size()>1) throw new DuplicateEmailExeption("Duplicate Email");
            asyncService.asyncTask(emailCertificationRequest);
            return EmailCertificationResponse.builder()
                        .code(SUCCESS_CODE)
                        .message(SUCCESS_MESSAGE)
                        .build();
    }

이렇게 되면 이메일 전송여부를 응답받기전에 200 OK를 내보내게된다
만약 이메일전송이 실패했을 경우에도 따로 예외처리를 해줘야한다
이건 다음에 다루도록 하겠다

profile
성장을 지향하는 개발자

0개의 댓글