2023. 8. 2. 19:42ㆍ트러블슈팅
https://developer-joon.tistory.com/162 스프링 이벤트 도입에 대한 글입니다.
[리팩토링] ILikeYou 프로젝트 스프링 이벤트, pub/sub구조 적용 (의존성 줄이기)
기능을 추가하다 보니, service 에서 다른 service와 결합도가 너무 높아졌다. 그리고 서비스 계층이 수행하는 일이 너무 많아져서 코드가 복잡해져서 가독성도 좋지 않았다. 그래서 코드를 기능(행
developer-joon.tistory.com
[문제발생 1]
ILikeYou 프로젝트에서 의존성을 낮추기 위해 스프링 이벤트를 적용하여서 코드를 분리했다.
하지만 이슈가 있음을 알 수 있었다.
@Transactional
public RsData<LikeablePerson> like(Member member, String username, int attractiveTypeCode) {
RsData<LikeablePerson> canLikeResult = validator.validateLike(member, username, attractiveTypeCode);
// S- : 성공 , F-0 : 수정가능, F- : 실패
//수정 코드 받은 경우
if (canLikeResult.getResultCode().equals("F-0")){
return modifyAttractiveTypeCode(attractiveTypeCode, canLikeResult.getData());
}
if (canLikeResult.isFail())
return canLikeResult;
InstaMember fromInstaMember = member.getInstaMember();
InstaMember toInstaMember = instaMemberService.findByUsernameOrCreate(username).getData();
LikeablePerson likeablePerson = createLikeablePerson(attractiveTypeCode, fromInstaMember, toInstaMember);
likeablePersonRepository.save(likeablePerson); // 저장
applicationEventPublisher.publishEvent(new EventAddLike(this, likeablePerson));
return RsData.of("S-1", "입력하신 인스타유저(%s)를 호감상대로 등록되었습니다.".formatted(username), likeablePerson);
}
코드를 보면 트랜잭션 내에서 save가 발생한 후 이벤트를 발생시킨다.
만약 트랜잭션이 비정상적으로 종료되어 롤백되었음에 불구하고,
이벤트를 구독해서 처리되는 부가적인 기능들은 별도로 처리되어 커밋되는 상황이 발생할 수도 있다.
[해결 1]
그래서 찾아보니, 스프링에서는 EventListener뿐 아니라 TransactionalEventListener을 제공한다.
TransactionalEventListener을 들어가보면 다음과 같은 내용을 볼 수 있다.
public @interface TransactionalEventListener {
/**
* Phase to bind the handling of an event to.
* <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}.
* <p>If no transaction is in progress, the event is not processed at
* all unless {@link #fallbackExecution} has been enabled explicitly.
*/
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
TransactionPhase 까지 타고 들어가면 phase는 4가지의 단계를 볼 수 있다.
그리고 default값은 AFTER_COMMIT이다. 즉, 커밋된 후에 이벤트를 발행한다는 것이다.
- AFTER_COMMIT (트랜잭션이 성공했을 때 실행)
- AFTER_ROLLBACK (트랜잭션 롤백시 실행)
- AFTER_COMPLETE 트랜잭션 완료시 (AFTER_COMMIT+AFTER_ROLLBACK)
- BEFORE_COMMIT (트랜잭션 commit 되기전에)
따라서 default로 냅두면 모든 것이 완벽할 것이라고 생각이 들었다.
[문제발생 2]
하지만 실제 디버깅 해보니,
mainProcess가 성공적으로 commit이 완료되었고, doAfterMainProcessCommit() 메소드가 실행되는 걸 디버깅을 통해 확인할 수 있었지만 실제 커밋 이후에 발생해야 하는 쿼리가 나가질 않았다.
[해결 2]
그래서 AFTER_COMMIT 에서 link가 걸려있는 TransactionSynchronization.afterCommit()에 대한 설명을 더 살펴보았다.
Use PROPAGATION_REQUIRES_NEW for any transactional operation that is called from here.
afterCommit에서 호출되는 작업에는 PROPAGATION_REQUIRES_NEW 을 사용하라고 한다.
즉, TransactionalEventListener에서 commit이 완료되었고 이후의 추가적인 변경 작업에 대해서는 commit이 발생하지 않기 때문에 afterCommit에서 트랜잭션 작업에는 PROPAGATION_REQUIRES_NEW 을 사용해야 한다.
최종적으로 EventListener은 아래와 같은 구조를 가지게 되었다.
@Component
@RequiredArgsConstructor
@Transactional(propagation = Propagation.REQUIRES_NEW)
public class NotificationEventListener {
private final NotificationService notificationService;
@TransactionalEventListener
public void listen(EventAddLike event) {
notificationService.afterAddLike(event.getLikeablePerson());
}
@TransactionalEventListener
public void listen(EventModifiedAttractiveType event) {
notificationService.afterModifyLike(event.getLikeablePerson(), event.getOldAttractiveTypeCode(), event.getNewAttractiveTypeCode());
}
}
'트러블슈팅' 카테고리의 다른 글
[트러블슈팅] TrendPick 프로젝트 주문 생성 시 상품 재고 처리 동시성 이슈 (1) | 2023.10.04 |
---|---|
[트러블슈팅] TrendPick 프로젝트 / 추천 시스템 Spring batch 도입 및 이슈 (0) | 2023.08.29 |
[트러블슈팅] ILikeYou 필터링 및 정렬 쿼리 JPQL -> Querydsl 코드개선작업 (0) | 2023.08.01 |
[트러블슈팅] TrendPick 프로젝트 추천 기능 구현 / 연관관계 및 쿼리 이슈 (0) | 2023.06.07 |