Skip to content

Commit 9f9b8c8

Browse files
authored
refactor: 이벤트 발행 과정에서 예외 발생 시 이벤트 발행되지 않도록 수정 (#675)
* test: 동시 참여 시 중복 참여 가능한 상황 테스트 * refactor: 공모 상태 업데이트 로직 이동 * refactor: 사용하지 않는 쿼리 제거 * feat: Offering 테이블 비관적 쓰기 락을 통해 중복 참여 방지 * test: 같은 공모 다른 사용자의 동시 참여 경우 테스트 * refactor: 비관적 쓰기 락 -> 낙관적 락을 통해 중복 참여 방지 * refactor: version 필드 추가로 인한 soft delete 쿼리 수정 * refactor: offeringMember 저장 시 offering의 실 저장 데이터 활용하도록 * refactor: 낙관적 락 -> 비관적 쓰기 락 * refactor: 호출 메서드 트랜잭션 롤백될 경우 발행된 이벤트 처리되지 않도록 * style: 다른 메서드와 변수 선언 순서 통일 * test: 동시 실행 코드 ConcurrencyExecutor 클래스로 추출
1 parent 0e74f45 commit 9f9b8c8

File tree

4 files changed

+63
-17
lines changed

4 files changed

+63
-17
lines changed

backend/src/main/java/com/zzang/chongdae/event/service/FcmEventListener.java

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.zzang.chongdae.event.service;
22

3+
import static org.springframework.transaction.event.TransactionPhase.AFTER_COMMIT;
4+
35
import com.zzang.chongdae.event.domain.CancelParticipateEvent;
46
import com.zzang.chongdae.event.domain.DeleteOfferingEvent;
57
import com.zzang.chongdae.event.domain.LoginEvent;
@@ -12,20 +14,21 @@
1214
import org.springframework.context.event.EventListener;
1315
import org.springframework.scheduling.annotation.Async;
1416
import org.springframework.stereotype.Component;
17+
import org.springframework.transaction.event.TransactionalEventListener;
1518

1619
@RequiredArgsConstructor
1720
@Component
1821
public class FcmEventListener {
1922

2023
private final FcmNotificationService notificationService;
2124

22-
@EventListener
25+
@TransactionalEventListener(phase = AFTER_COMMIT)
2326
@Async
2427
public void handleParticipateEvent(ParticipateEvent event) {
2528
notificationService.participate(event.getOfferingMember());
2629
}
2730

28-
@EventListener
31+
@TransactionalEventListener(phase = AFTER_COMMIT)
2932
@Async
3033
public void handleCancelParticipateEvent(CancelParticipateEvent event) {
3134
notificationService.cancelParticipation(event.getOfferingMember());
@@ -37,7 +40,7 @@ public void handleSaveOfferingEvent(SaveOfferingEvent event) {
3740
notificationService.saveOffering(event.getOffering());
3841
}
3942

40-
@EventListener
43+
@TransactionalEventListener(phase = AFTER_COMMIT)
4144
@Async
4245
public void handleDeleteOfferingEvent(DeleteOfferingEvent event) {
4346
notificationService.deleteOffering(event.getOffering());
@@ -49,13 +52,13 @@ public void handleSaveCommentEvent(SaveCommentEvent event) {
4952
notificationService.saveComment(event.getComment(), event.getOfferingMembers());
5053
}
5154

52-
@EventListener
55+
@TransactionalEventListener(phase = AFTER_COMMIT)
5356
@Async
5457
public void handleUpdateStatusEvent(UpdateStatusEvent event) {
5558
notificationService.updateStatus(event.getOffering());
5659
}
5760

58-
@EventListener
61+
@TransactionalEventListener(phase = AFTER_COMMIT)
5962
@Async
6063
public void handleLoginEvent(LoginEvent event) {
6164
notificationService.login(event.getMember());

backend/src/main/java/com/zzang/chongdae/notification/service/FcmNotificationService.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@
1515
import java.util.List;
1616
import javax.annotation.Nullable;
1717
import lombok.RequiredArgsConstructor;
18-
import lombok.extern.slf4j.Slf4j;
1918
import org.springframework.stereotype.Service;
2019

21-
@Slf4j
2220
@RequiredArgsConstructor
2321
@Service
2422
public class FcmNotificationService {
@@ -50,9 +48,9 @@ public void updateStatus(OfferingEntity offering) {
5048
}
5149

5250
public void saveOffering(OfferingEntity offering) {
53-
Message message = offeringMessageManager.messageWhenSaveOffering(offering);
5451
FcmTopic topic = FcmTopic.proposerTopic(offering);
5552
notificationSubscriber.subscribe(offering.getMember(), topic);
53+
Message message = offeringMessageManager.messageWhenSaveOffering(offering);
5654
notificationSender.send(message);
5755
}
5856

backend/src/test/java/com/zzang/chongdae/event/service/FcmEventListenerTest.java

+25-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.zzang.chongdae.event.service;
22

3+
import static org.mockito.ArgumentMatchers.any;
34
import static org.mockito.Mockito.mock;
45
import static org.mockito.Mockito.times;
56
import static org.mockito.Mockito.verify;
@@ -16,12 +17,11 @@
1617
import org.junit.jupiter.api.Test;
1718
import org.springframework.beans.factory.annotation.Autowired;
1819
import org.springframework.boot.test.mock.mockito.MockBean;
19-
import org.springframework.context.ApplicationEventPublisher;
2020

2121
class FcmEventListenerTest extends ServiceTest {
2222

2323
@Autowired
24-
ApplicationEventPublisher eventPublisher;
24+
TestEventPublisher testEventPublisher;
2525

2626
@MockBean
2727
FcmEventListener eventListener;
@@ -33,7 +33,7 @@ void should_executeEvent_when_publishParticipateEvent() {
3333
ParticipateEvent event = mock(ParticipateEvent.class);
3434

3535
// when
36-
eventPublisher.publishEvent(event);
36+
testEventPublisher.publishWithTransaction(event);
3737

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

4848
// when
49-
eventPublisher.publishEvent(event);
49+
testEventPublisher.publishWithTransaction(event);
5050

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

6161
// when
62-
eventPublisher.publishEvent(event);
62+
testEventPublisher.publishWithoutTransaction(event);
6363

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

7474
// when
75-
eventPublisher.publishEvent(event);
75+
testEventPublisher.publishWithTransaction(event);
7676

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

8787
// when
88-
eventPublisher.publishEvent(event);
88+
testEventPublisher.publishWithoutTransaction(event);
8989

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

100100
// when
101-
eventPublisher.publishEvent(event);
101+
testEventPublisher.publishWithTransaction(event);
102102

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

113113
// when
114-
eventPublisher.publishEvent(event);
114+
testEventPublisher.publishWithTransaction(event);
115115

116116
// then
117117
verify(eventListener, times(1)).handleLoginEvent(event);
118118
}
119+
120+
@DisplayName("이벤트 발행 후 예외가 발생한 경우 이벤트 로직을 실행하지 않는다.")
121+
@Test
122+
void should_notExecuteEvent_when_throwException() {
123+
// given
124+
LoginEvent event = mock(LoginEvent.class);
125+
126+
// when
127+
try {
128+
testEventPublisher.publishWithTransactionThenThrowException(event);
129+
} catch (Exception ignored) {
130+
}
131+
132+
// then
133+
verify(eventListener, times(0)).handleLoginEvent(any(LoginEvent.class));
134+
}
119135
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.zzang.chongdae.event.service;
2+
3+
import org.springframework.beans.factory.annotation.Autowired;
4+
import org.springframework.context.ApplicationEvent;
5+
import org.springframework.context.ApplicationEventPublisher;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.transaction.annotation.Transactional;
8+
9+
@Component
10+
public class TestEventPublisher {
11+
12+
@Autowired
13+
ApplicationEventPublisher eventPublisher;
14+
15+
public void publishWithoutTransaction(ApplicationEvent event) {
16+
eventPublisher.publishEvent(event);
17+
}
18+
19+
@Transactional
20+
public void publishWithTransaction(ApplicationEvent event) {
21+
eventPublisher.publishEvent(event);
22+
}
23+
24+
@Transactional
25+
public void publishWithTransactionThenThrowException(ApplicationEvent event) {
26+
eventPublisher.publishEvent(event);
27+
throw new RuntimeException();
28+
}
29+
}

0 commit comments

Comments
 (0)