Skip to content

Commit

Permalink
refactor: 이벤트 발행 과정에서 예외 발생 시 이벤트 발행되지 않도록 수정 (#675)
Browse files Browse the repository at this point in the history
* test: 동시 참여 시 중복 참여 가능한 상황 테스트

* refactor: 공모 상태 업데이트 로직 이동

* refactor: 사용하지 않는 쿼리 제거

* feat: Offering 테이블 비관적 쓰기 락을 통해 중복 참여 방지

* test: 같은 공모 다른 사용자의 동시 참여 경우 테스트

* refactor: 비관적 쓰기 락 -> 낙관적 락을 통해 중복 참여 방지

* refactor: version 필드 추가로 인한 soft delete 쿼리 수정

* refactor: offeringMember 저장 시 offering의 실 저장 데이터 활용하도록

* refactor: 낙관적 락 -> 비관적 쓰기 락

* refactor: 호출 메서드 트랜잭션 롤백될 경우 발행된 이벤트 처리되지 않도록

* style: 다른 메서드와 변수 선언 순서 통일

* test: 동시 실행 코드 ConcurrencyExecutor 클래스로 추출
  • Loading branch information
helenason authored Dec 24, 2024
1 parent 0e74f45 commit 9f9b8c8
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.zzang.chongdae.event.service;

import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT;

import com.zzang.chongdae.event.domain.CancelParticipateEvent;
import com.zzang.chongdae.event.domain.DeleteOfferingEvent;
import com.zzang.chongdae.event.domain.LoginEvent;
Expand All @@ -12,20 +14,21 @@
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionalEventListener;

@RequiredArgsConstructor
@Component
public class FcmEventListener {

private final FcmNotificationService notificationService;

@EventListener
@TransactionalEventListener(phase = AFTER_COMMIT)
@Async
public void handleParticipateEvent(ParticipateEvent event) {
notificationService.participate(event.getOfferingMember());
}

@EventListener
@TransactionalEventListener(phase = AFTER_COMMIT)
@Async
public void handleCancelParticipateEvent(CancelParticipateEvent event) {
notificationService.cancelParticipation(event.getOfferingMember());
Expand All @@ -37,7 +40,7 @@ public void handleSaveOfferingEvent(SaveOfferingEvent event) {
notificationService.saveOffering(event.getOffering());
}

@EventListener
@TransactionalEventListener(phase = AFTER_COMMIT)
@Async
public void handleDeleteOfferingEvent(DeleteOfferingEvent event) {
notificationService.deleteOffering(event.getOffering());
Expand All @@ -49,13 +52,13 @@ public void handleSaveCommentEvent(SaveCommentEvent event) {
notificationService.saveComment(event.getComment(), event.getOfferingMembers());
}

@EventListener
@TransactionalEventListener(phase = AFTER_COMMIT)
@Async
public void handleUpdateStatusEvent(UpdateStatusEvent event) {
notificationService.updateStatus(event.getOffering());
}

@EventListener
@TransactionalEventListener(phase = AFTER_COMMIT)
@Async
public void handleLoginEvent(LoginEvent event) {
notificationService.login(event.getMember());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
import java.util.List;
import javax.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@RequiredArgsConstructor
@Service
public class FcmNotificationService {
Expand Down Expand Up @@ -50,9 +48,9 @@ public void updateStatus(OfferingEntity offering) {
}

public void saveOffering(OfferingEntity offering) {
Message message = offeringMessageManager.messageWhenSaveOffering(offering);
FcmTopic topic = FcmTopic.proposerTopic(offering);
notificationSubscriber.subscribe(offering.getMember(), topic);
Message message = offeringMessageManager.messageWhenSaveOffering(offering);
notificationSender.send(message);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.zzang.chongdae.event.service;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand All @@ -16,12 +17,11 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.ApplicationEventPublisher;

class FcmEventListenerTest extends ServiceTest {

@Autowired
ApplicationEventPublisher eventPublisher;
TestEventPublisher testEventPublisher;

@MockBean
FcmEventListener eventListener;
Expand All @@ -33,7 +33,7 @@ void should_executeEvent_when_publishParticipateEvent() {
ParticipateEvent event = mock(ParticipateEvent.class);

// when
eventPublisher.publishEvent(event);
testEventPublisher.publishWithTransaction(event);

// then
verify(eventListener, times(1)).handleParticipateEvent(event);
Expand All @@ -46,7 +46,7 @@ void should_executeEvent_when_publishParticipateCancelEvent() {
CancelParticipateEvent event = mock(CancelParticipateEvent.class);

// when
eventPublisher.publishEvent(event);
testEventPublisher.publishWithTransaction(event);

// then
verify(eventListener, times(1)).handleCancelParticipateEvent(event);
Expand All @@ -59,7 +59,7 @@ void should_executeEvent_when_publishSaveOfferingEvent() {
SaveOfferingEvent event = mock(SaveOfferingEvent.class);

// when
eventPublisher.publishEvent(event);
testEventPublisher.publishWithoutTransaction(event);

// then
verify(eventListener, times(1)).handleSaveOfferingEvent(event);
Expand All @@ -72,7 +72,7 @@ void should_executeEvent_when_publishDeleteOfferingEvent() {
DeleteOfferingEvent event = mock(DeleteOfferingEvent.class);

// when
eventPublisher.publishEvent(event);
testEventPublisher.publishWithTransaction(event);

// then
verify(eventListener, times(1)).handleDeleteOfferingEvent(event);
Expand All @@ -85,7 +85,7 @@ void should_executeEvent_when_publishSaveCommentEvent() {
SaveCommentEvent event = mock(SaveCommentEvent.class);

// when
eventPublisher.publishEvent(event);
testEventPublisher.publishWithoutTransaction(event);

// then
verify(eventListener, times(1)).handleSaveCommentEvent(event);
Expand All @@ -98,7 +98,7 @@ void should_executeEvent_when_publishUpdateStatusEvent() {
UpdateStatusEvent event = mock(UpdateStatusEvent.class);

// when
eventPublisher.publishEvent(event);
testEventPublisher.publishWithTransaction(event);

// then
verify(eventListener, times(1)).handleUpdateStatusEvent(event);
Expand All @@ -111,9 +111,25 @@ void should_executeEvent_when_publishLoginEvent() {
LoginEvent event = mock(LoginEvent.class);

// when
eventPublisher.publishEvent(event);
testEventPublisher.publishWithTransaction(event);

// then
verify(eventListener, times(1)).handleLoginEvent(event);
}

@DisplayName("이벤트 발행 후 예외가 발생한 경우 이벤트 로직을 실행하지 않는다.")
@Test
void should_notExecuteEvent_when_throwException() {
// given
LoginEvent event = mock(LoginEvent.class);

// when
try {
testEventPublisher.publishWithTransactionThenThrowException(event);
} catch (Exception ignored) {
}

// then
verify(eventListener, times(0)).handleLoginEvent(any(LoginEvent.class));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.zzang.chongdae.event.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class TestEventPublisher {

@Autowired
ApplicationEventPublisher eventPublisher;

public void publishWithoutTransaction(ApplicationEvent event) {
eventPublisher.publishEvent(event);
}

@Transactional
public void publishWithTransaction(ApplicationEvent event) {
eventPublisher.publishEvent(event);
}

@Transactional
public void publishWithTransactionThenThrowException(ApplicationEvent event) {
eventPublisher.publishEvent(event);
throw new RuntimeException();
}
}

0 comments on commit 9f9b8c8

Please sign in to comment.