영호

[Spring] 외부 API 와 비즈니스 로직 분리하기 본문

Spring

[Spring] 외부 API 와 비즈니스 로직 분리하기

0h0 2023. 10. 15. 18:27

들어가면서,

프로젝트 도중, 아래의 요구사항을 구현하면서 @TransactionalEventListner 를 사용해 기존 코드의 문제점이었던 코드의 유지보수성불필요한 로직의 트랜잭션을 제거한 과정을 기록하려고 한다.

스탬프 적립이 정상적으로 완료되면, 슬랙으로 알림을 보낸다.
알림 발송에 실패해도 스탬프 적립이 롤백되어선 안된다.

기존 코드

@Transactional
public void createStamp(StampCreateDto stampCreateDto) {
    // 스탬프 적립 전 여러 검증 코드
    coupon.accumulate(earningStampCount);
    // 슬랙 API 호출
}

기존 코드의 문제점

기존 코드는 아래와 같은 문제점이 있다고 판단했다.

  1. 외부 API 와 비즈니스 로직이 매우 강하게 결합되어 있다.
    • 만약 외부 API 로직이 복잡하다면 다른 개발자가 봤을 때 해당 메서드의 역할을 명확하게 파악하기 힘들다.
  2. 외부(슬랙) API 호출 과정에서 예외가 발생한다면 스탬프 적립까지 롤백된다.
  3. 외부(슬랙) API 호출이 동기적으로 이루어지면서 API 응답 속도가 불필요하게 느려진다.

[1번 문제 해결] 비즈니스 로직과 외부 API의 강한 결합 해결

이 부분은 EventListner 를 사용해 해결했다. 스탬프 적립 알림 발송스탬프 적립 비즈니스 로직의 관심사가 아니라고 생각했다.

그래서 슬랙 API 호출 부분을 분리하기 위한 방법을 고민하다가 @EventListener 를 알게되어 이를 사용하기로 했다.

코드는 아래와 같다.

@Transactional
public void createStamp(StampCreateDto stampCreateDto) {
    // 스탬프 적립 전 여러 검증 코드
		coupon.accumulate(earningStampCount);

		eventPublisher.publishEvent(new StampCreateEvent());
}

이를 통해 비즈니스 로직과 외부 API 가 강하게 결합되어 있던 코드를 개선할 수 있었다. 이제 해당 메서드를 보고 핵심 관심사가 무엇인지 파악하기 용이해졌다.

그러나, 아직 문제점은 남아있다.

  1. 외부 API 호출 과정에서 예외 발생 시 스탬프 적립도 롤백된다.
  2. @EventListner 는 동기적으로 실행되기 때문에 스탬프 적립 관련 비즈니스 로직(슬랙 알림 제외)이 완료되어도 외부 API 호출 작업이 완료될 때 까지 기다리게 된다.

[2번 문제 상황] 외부 API로 인한 롤백 문제 상황

이 문제는 외부 API 로직이 이벤트를 발행하는 서비스 메서드의 트랜잭션을 같이 공유하면서 발생하는 문제다. 이를 해결하기 위해 @TransactionalEventListner 를 활용했다.

롤백 문제 상황을 간단하게 도식화 하면 아래와 같다. @EventListner 를 사용했을 때의 상황이다.

  1. 이벤트 발행 시 이를 구독하고 있는 Listner 가 동일한 스레드에서 동기적으로 실행된다.
  2. 1번의 상황으로 인해 기존 트랜잭션을 그대로 사용하게 된다.
  3. 구독자 측에서 발생한 runtime 예외로 인해 트랜잭션에 rollback 마킹이 된다.
  4. 트랜잭션 롤백 작업 수행

이를 해결하기 위해 @TransactionalEventListner, @Async 를 활용할 수 있다.

[2, 3번 문제 해결]@TransactionalEventListner, @Async 를 활용한 해결

@TransactionalEventListner 는 다양한 옵션이 있는데, 그 중 AFTER_COMMIT 옵션을 활용했다. 그 이유는 간단하다.

  • 이벤트를 발행하는 서비스 트랜잭션이 commit 되었단 것은 정상적으로 로직 수행이 완료됐다는 의미이다.
    • 이벤트 발행 로직이 정상적으로 commit 된 후 외부 API 를 호출하면 외부 API의 성공 여부에 관계 없이 비즈니스 로직에서 저장한 데이터는 남아있게 된다.

@Async

이를 통해 동기 문제도 동시에 해결할 수 있다. 이 어노테이션으로 슬랙 API 는 비즈니스 로직과 별도의 스레드에서 수행된다.

 

잘못된 부분 있으면 언제든 댓글 달아주세요~

Comments