-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: 비동기 + 이벤트 리스닝 방식으로 알림 로직 개선 #906
base: develop
Are you sure you want to change the base?
Conversation
sendGeneralMessage > sendGeneralMessage2
Test Results 55 files +2 55 suites +2 8s ⏱️ +2s Results for commit eb4f49a. ± Comparison against base commit a7ba2fd. This pull request removes 9 and adds 17 tests. Note that renamed tests count towards both.
|
📝 Test Coverage Report
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
�전반적으로 깔끔하게 코드를 개선한 것 같아요 👍
알림 전송에 대해서 비동기 처리가 필요했었는데 개선해주셨네요 😄
코드 리뷰하면서 이벤트 리스너를 꼭 사용해야하는지가 궁금했던 것 같아요
관련해서 질문들 남겼습니다 !
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //스레드풀이 가득찼을 경우 톰캣 스레드에서 처리 | ||
int coreCount = Runtime.getRuntime().availableProcessors(); | ||
executor.setCorePoolSize(coreCount); | ||
executor.setThreadNamePrefix("ody-fcm"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[제안]
지금 대로면 fcm 관련 로직이 아닌 곳에서 @Asnyc를 사용할 경우데 해당 스레드가 ody-fcm으로 표시되서
혼란이 발생할 수 있을 것 같아요
해당 Executor의 Bean 이름을 fcm으로 네이밍해주고 fcm 관련 비동기 처리는 해당 Bean을 사용하게 하면 어떨까요 ?
@EnableAsync | ||
@Configuration | ||
@RequiredArgsConstructor | ||
public class AsyncConfig implements AsyncConfigurer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[질문]
AsyncConfigurer
인터페이스를 구현해 설정을 구성했는데
직접 Bean으로 등록해 등록해주는 것 보다 장점이 있을까요??
만약 비동기 처리에 대한 정책들이 각 로직마다 다르다면 (밑에 리뷰와 같은 상황에서)
다형성 Bean으로 관리하게 될 것 같아 Bean 방식이 유연하지 않을까 해서
추가적인 장점이 있는지 궁금해서 여쭙니다 😄
|
||
@Slf4j | ||
@Component | ||
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오호 이렇게 비동기 처리 에러를 로깅할 수 있군요 👍
Notification memberDeletionNotification = Notification.createMemberDeletion(mate); | ||
notificationService.save(memberDeletionNotification); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
메서드의 역할에 맞게 분리되었네요 👍
@Async | ||
@Transactional(propagation = Propagation.REQUIRES_NEW) // 추가 커밋 허용을 위해 트랜잭션을 염 | ||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)// 커밋 이후 발송 | ||
public void sendPushMessage(PushEvent pushEvent) { | ||
Notification notification = pushEvent.getNotification(); | ||
GroupMessage groupMessage = GroupMessage.from(notification); | ||
fcmPushSender.sendGeneralMessage(groupMessage.message(), notification); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[질문]
Async도 프록시 객체가 생성되고 Transactional도 프록시 객체가 생성되서
두 어노테이션을 같이 사용하면 롤백이 안되는 등의 이슈가 있는 것으로 알고 있어요
추가 커밋 허용을 위해 트랜잭션을 열었다고 했는데
sendGeneralMessage() 메서드에 트랜잭션이 선언되어 있어 문제가 없지 않을까요 ??
- 추가 질문
현재 알림 로직을 호출하는 메서드들은
비즈니스로직을 처리 후에 마지막에 알림 로직을 호출하고 있는데
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
가 필요�했던 이유가
궁금합니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가 커밋 허용을 위해 트랜잭션을 열었다고 했는데
sendGeneralMessage() 메서드에 트랜잭션이 선언되어 있어 문제가 없지 않을까요 ??
저도 이부분 궁금합니다!
@Transactional(propagation = Propagation.REQUIRES_NEW)
가 불필요해 보여요!
} | ||
|
||
private void sendGeneralMessage(Message message, Notification notification) { | ||
public void sendGeneralMessage(Message message, Notification notification) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[질문]
topic, divice token 메세지를 모두 일반 메세지로 표현하신 것 같은데
일반적이지 않은 메세지는 뭐라고 생각하시나요 ??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[제안]
공지(Notice)와 알림(PushMessage, NudgeMessage)으로 구분되는 것 같아요. general 이란 표현이 이전에 사용된 적이 없어 보여서, 의미가 애매하게 느껴지긴 해요.
sendNotificationMessage()
로 바꾸면 어떨까요?
.then() | ||
.statusCode(201); | ||
|
||
Thread.sleep(1000L); //비동기 메서드 유휴 시간 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[제안]
비동기 처리가 실행 환경에 따라 1초보다 더 걸릴 수 있으니
sleep으로 테스트하면 비결정적인 테스트가 될 것 같아요...
CountDownLatch를 사용해 결정적인 테스트로 수정해보면 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
콜리의 경우 전체 테스트가 20초가 걸렸던데,
CountDownLatch
사용하면 더 짧아질 수도 있어서 적용하면 좋을 것 같아요
import org.springframework.context.ApplicationEvent; | ||
|
||
@Getter | ||
public class NoticeEvent extends ApplicationEvent { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spring 프레임워크 4.2 버전 이후 부터는 ApplicationEvent
를 상속받지 않아도
이벤트 객체를 생성 가능하다고 하네요!
https://www.baeldung.com/spring-events
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[의견]
카키가 말해준 것처럼, ApplicationEvent
를 상속받지 않아도 되지만
Event들을 묶어줄 수 있는 부모 클래스가 있는게 다형성을 활용할 수 있어, 상속을 빼지 않아도 좋을 것 같아요. 물론 NotificationEvent
같은 다른 상위 클래스를 만들어도 좋구요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
콜리 고생하셨어요! 비동기 개선이 합리적이라고 생각해요. 덕분에 비동기 부분 공부해볼 수 있었어요👍
다만, 기존 동기 방식을 앱으로 사용하면서 딜레이가 있다고 느끼진 않았었는데, 메시지 전송 시간 3초 측정은 어떻게 하셨나요?
@Transactional | ||
public void scheduleNotification(Notification notification) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[질문]
public & 트랜잭션을 명시한 이유가 궁금해요. 외부에서 사용될 가능성이 높은 메서드라 열어두신 걸까요?
상위 메서드에서 트랜잭션이 열려있어 의미가 없어 보여요. 해당 메서드는 같은 클래스에서만 호출되고 있는데, private 이어도 되지 않을까요?
|
||
List<Notification> allMeetingIdAndType = notificationRepository.findAllMeetingIdAndType(meeting.getId(), | ||
NotificationType.DEPARTURE_REMINDER); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[필수]
사용하지 않는 라인이네요! 추가한 이유가 있나요? 불필요한 DB 접근이면 삭제해주는게 좋을 것 같아요~
Instant startTime = InstantConverter.kstToInstant(etaNoticeTime); | ||
taskScheduler.schedule(() -> fcmPushSender.sendNoticeMessage(noticeMessage), startTime); | ||
log.info("{} 타입 알림 {}에 스케줄링 예약", NotificationType.ETA_NOTICE, InstantConverter.instantToKst(startTime)); | ||
notificationService.scheduleNotice(noticeMessage, etaNoticeTime); | ||
log.info("{} 타입 알림 {}에 스케줄링 예약", NotificationType.ETA_NOTICE, etaNoticeTime); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 알림은 NotificationService를 거치도록 일관성이 생겼네요
} | ||
|
||
private void sendGeneralMessage(Message message, Notification notification) { | ||
public void sendGeneralMessage(Message message, Notification notification) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[제안]
공지(Notice)와 알림(PushMessage, NudgeMessage)으로 구분되는 것 같아요. general 이란 표현이 이전에 사용된 적이 없어 보여서, 의미가 애매하게 느껴지긴 해요.
sendNotificationMessage()
로 바꾸면 어떨까요?
import org.springframework.context.ApplicationEvent; | ||
|
||
@Getter | ||
public class NoticeEvent extends ApplicationEvent { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[의견]
카키가 말해준 것처럼, ApplicationEvent
를 상속받지 않아도 되지만
Event들을 묶어줄 수 있는 부모 클래스가 있는게 다형성을 활용할 수 있어, 상속을 빼지 않아도 좋을 것 같아요. 물론 NotificationEvent
같은 다른 상위 클래스를 만들어도 좋구요!
import org.springframework.test.context.ActiveProfiles; | ||
import org.springframework.test.context.event.RecordApplicationEvents; | ||
|
||
class AsyncExceptionHandlerTest extends BaseControllerTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
트랜잭션 관련 로직을 통합 테스트에서 검증하는 것이 어색하다고 생각하겠지만, 우선 MateControllerTest에 작성했습니다. 위치가 어울리지 않는다고 생각하면 피드백 남겨주세요!
계층 관점에서 전 합리적이라고 생각해요!
} | ||
|
||
; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오잉??
} | |
; | |
} |
@DisplayName("바로 전송해야 하는 입장/출발 알림의 경우, 트랜잭션이 완료 된 후 알림을 조회한다") | ||
@Test | ||
void sendUnSavedMessage() throws InterruptedException, FirebaseMessagingException { | ||
Meeting nowMeeting = fixtureGenerator.generateMeeting(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅
Meeting nowMeeting = fixtureGenerator.generateMeeting(); | |
Meeting nowMeeting = fixtureGenerator.generateMeeting(); |
.then() | ||
.statusCode(201); | ||
|
||
Thread.sleep(1000L); //비동기 메서드 유휴 시간 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
콜리의 경우 전체 테스트가 20초가 걸렸던데,
CountDownLatch
사용하면 더 짧아질 수도 있어서 적용하면 좋을 것 같아요
verify(fcmEventListener, times(1)).sendPushMessage(eq(pushEvent)); | ||
} | ||
|
||
@DisplayName("트랜잭션이 열리지 않으면, 푸시 알림 발송 로직이 실행되지 않는다") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
꼼꼼한 테스트👍
🚩 연관 이슈
close #905
📝 작업 내용
🏞️ 스크린샷 (선택)
🗣️ 리뷰 요구사항 (선택)
관련 설명은 노션 페이지에서 볼 수 있습니다. 리팩터링 양이 꽤 되어서 꼭 참고해주세요!
Approve&Merge되지 않아도 괜찮아요. 합리적이라 생각하는지에 대한 의견도 이야기해주시면 좋을 것 같아요.