이전 포스팅에서 REST Application 에서 비동기가 필요한 경우와 실제로 동기로 로직을 처리하는 API와 비동기로 로직을 처리하는 API를 간단하게 만들어보면서 테스트를 진행해보았다.
이번 포스팅에선 실제로 서비스에서 생길 수 있는 문제를 정의해보고 이를 해결하는 과정을 진행할 것이다.
한 서비스에서 커플끼리 공유 다이어리를 사용하는 기능을 제공한다.
남자친구가 게시물을 게시했을 때, 여자친구에게 게시글 등록을 알려주는 이메일을 보낸다.
위 요구사항에 맞게 간략하게 흐름을 그림으로 구성을 해보았다.
흐름은 다음과 같다.
코드를 통해서 보면 다음과 같다.
DiaryController.class
uploadDiary()
를 통해 diaryService의createPost()
를 호출한다.
DiaryService.class
(1)
diaryRepository.save()
를 통해 일기를 저장하고,
(2)mailService.sendMail()
을 통해 메일을 보낸다.
그냥 요구사항에 맞게 구현해보면 다음과 같이 구현할 수 있을 것이다.
하지만, 이는 요구사항을 이상적으로 구현한 케이스이다. 실제로 우리는 다음과 같은 상황들을 마주할 수 있다.
메일 전송 실패 , 메일 전송의 시간 지연
위 경우에는 어떠한 문제가 발생할까?
메일 전송이 실패할 경우엔, createPost
메서드는 @Transactional
로 인해 하나의 트랜잭션으로 묶여 있기 때문에 일기 저장도 롤백(rollback)되는 문제가 발생할 것이다.
또한, 메일 전송에서 시간이 지연되면 그만큼 일기 작성 API를 호출한 사용자 입장에서 ‘왜 이렇게 작성 완료가 오래 걸려?’ 하는 불편함을 야기할 것이다.
이는 일기 작성 API의 주요 관심사가 아닌 보조적인 요구사항 ‘메일 전송’
에 의해 주요 관심사인 ‘일기 작성'
행위에 영향을 주는 문제라고 정의할 수 있다.
다양한 API의 비즈니스 로직에 메일 전송 로직 추가
위 경우에는 어떠한 문제가 발생할까?
다양한 API의 비즈니스 로직을 수행하는 서비스 메서드에 sendEmail()
메서드를 호출하는 코드를 작성해야 하고, 그러면 중복 코드가 계속 발생할 것이다.
위에서 이야기를 한 것 처럼 메일 전송
은 주요 로직이 아니다. 그저 서비스 요구사항에 따라 추가될 수 도, 삭제될 수 도 있는 로직이다.
첫 번째 문제<메일 전송 실패 , 메일 전송의 시간 지연>의 경우,
sendMail() 메서드를 비동기로 처리함으로써 해결할 수 있을 것이며
두 번째 문제 <다양한 API의 비즈니스 로직에 메일 전송 로직 추가>의 경우,
AOP 를 통해서 문제를 해결할 수 있을 것이다.
메일 전송 로직을 비동기로 처리하기 위한 추가 요구사항은 다음과 같다.
다이어리가 생성이 완료되면, 메일 전송은 비동기로 처리한다.
먼저
위 두가지를 수행하는 모듈이 서로에 의존하지 않도록 하고, Controller 와의 의존성을 없애기 위해 퍼사드 패턴을 적용해보았다.
DiaryFacade
클래스를 생성하고 writeDiary()
메서드에서 DiaryService
의 createPost()
메서드와 MailService
의 sendMail()
메서드를 호출하도록 하였다.
DiaryFacade.class
CompletableFuture 객체를 활용하여 다이어리 생성이 완료되면
CompletableFuture<DiaryResponse> postFuture = CompletableFuture.supplyAsync(
() -> diaryService.createPost(diaryRequest));
메일 전송을 비동기로 처리하도록 구현한다.
postFuture.thenAcceptAsync(post -> mailService.sendMail());
위와 같이 비동기 처리를 통해 메일 전송이 다이어리 생성에 영향을 끼치는 부분을 제거할 수 있게 되었다.
두 번째 문제 <다양한 API의 비즈니스 로직에 메일 전송 로직 추가> 을 해결하기 위해서 AOP를 적용하는 부분은 다음 포스팅 이후에 다뤄보도록 하겠다.