영호
[Spring] 외부 API 와 비즈니스 로직 분리하기 본문
들어가면서,
프로젝트 도중, 아래의 요구사항을 구현하면서 @TransactionalEventListner 를 사용해 기존 코드의 문제점이었던 코드의 유지보수성과 불필요한 로직의 트랜잭션을 제거한 과정을 기록하려고 한다.
스탬프 적립이 정상적으로 완료되면, 슬랙으로 알림을 보낸다.
알림 발송에 실패해도 스탬프 적립이 롤백되어선 안된다.
기존 코드
@Transactional
public void createStamp(StampCreateDto stampCreateDto) {
// 스탬프 적립 전 여러 검증 코드
coupon.accumulate(earningStampCount);
// 슬랙 API 호출
}
기존 코드의 문제점
기존 코드는 아래와 같은 문제점이 있다고 판단했다.
- 외부 API 와 비즈니스 로직이 매우 강하게 결합되어 있다.
- 만약 외부 API 로직이 복잡하다면 다른 개발자가 봤을 때 해당 메서드의 역할을 명확하게 파악하기 힘들다.
- 외부(슬랙) API 호출 과정에서 예외가 발생한다면 스탬프 적립까지 롤백된다.
- 외부(슬랙) API 호출이 동기적으로 이루어지면서 API 응답 속도가 불필요하게 느려진다.
[1번 문제 해결] 비즈니스 로직과 외부 API의 강한 결합 해결
이 부분은 EventListner 를 사용해 해결했다. 스탬프 적립 알림 발송은 스탬프 적립 비즈니스 로직의 관심사가 아니라고 생각했다.
그래서 슬랙 API 호출 부분을 분리하기 위한 방법을 고민하다가 @EventListener 를 알게되어 이를 사용하기로 했다.
코드는 아래와 같다.
@Transactional
public void createStamp(StampCreateDto stampCreateDto) {
// 스탬프 적립 전 여러 검증 코드
coupon.accumulate(earningStampCount);
eventPublisher.publishEvent(new StampCreateEvent());
}
이를 통해 비즈니스 로직과 외부 API 가 강하게 결합되어 있던 코드를 개선할 수 있었다. 이제 해당 메서드를 보고 핵심 관심사가 무엇인지 파악하기 용이해졌다.
그러나, 아직 문제점은 남아있다.
- 외부 API 호출 과정에서 예외 발생 시 스탬프 적립도 롤백된다.
- @EventListner 는 동기적으로 실행되기 때문에 스탬프 적립 관련 비즈니스 로직(슬랙 알림 제외)이 완료되어도 외부 API 호출 작업이 완료될 때 까지 기다리게 된다.
[2번 문제 상황] 외부 API로 인한 롤백 문제 상황
이 문제는 외부 API 로직이 이벤트를 발행하는 서비스 메서드의 트랜잭션을 같이 공유하면서 발생하는 문제다. 이를 해결하기 위해 @TransactionalEventListner 를 활용했다.
롤백 문제 상황을 간단하게 도식화 하면 아래와 같다. @EventListner 를 사용했을 때의 상황이다.
- 이벤트 발행 시 이를 구독하고 있는 Listner 가 동일한 스레드에서 동기적으로 실행된다.
- 1번의 상황으로 인해 기존 트랜잭션을 그대로 사용하게 된다.
- 구독자 측에서 발생한 runtime 예외로 인해 트랜잭션에 rollback 마킹이 된다.
- 트랜잭션 롤백 작업 수행
이를 해결하기 위해 @TransactionalEventListner, @Async 를 활용할 수 있다.
[2, 3번 문제 해결]@TransactionalEventListner, @Async 를 활용한 해결
@TransactionalEventListner 는 다양한 옵션이 있는데, 그 중 AFTER_COMMIT 옵션을 활용했다. 그 이유는 간단하다.
- 이벤트를 발행하는 서비스 트랜잭션이 commit 되었단 것은 정상적으로 로직 수행이 완료됐다는 의미이다.
- 이벤트 발행 로직이 정상적으로 commit 된 후 외부 API 를 호출하면 외부 API의 성공 여부에 관계 없이 비즈니스 로직에서 저장한 데이터는 남아있게 된다.
@Async
이를 통해 동기 문제도 동시에 해결할 수 있다. 이 어노테이션으로 슬랙 API 는 비즈니스 로직과 별도의 스레드에서 수행된다.
잘못된 부분 있으면 언제든 댓글 달아주세요~
'Spring' 카테고리의 다른 글
스탬프 중복 적립 개선기 (0) | 2024.02.23 |
---|---|
[Spring] @TransactionalEventListener 에서 CUD 가 안되는 이유 (0) | 2023.10.15 |
[Spring] @Transactional 의 REQUIRED, REQUIRES_NEW 트랜잭션 생성 과정 (1) | 2023.10.14 |
[Spring] @Transactional 물리 트랜잭션과 논리 트랜잭션 커밋 동작 차이 (2) | 2023.10.14 |
하나의 객체에서 여러 Builder 사용해보기 (feat. Builder 컴파일 과정, lombok) (2) | 2023.09.07 |