-
Notifications
You must be signed in to change notification settings - Fork 1
[feat] #315 - 회원가입 시 Slack으로 알림을 보내는 비동기 이벤트 리스너 구현 #316
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
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
7aab517
[#315] refactor: dto 패키지 application 패키지 하위로 이동
move-hoon 3fe3d08
[#315] refactor: 응답 dto response 패키지로 이동
move-hoon 7ea24d8
[#315] chore(build.gradle): slack webhook 및 @ConfigurationProperties을…
move-hoon 82dbafd
[#315] chore: slack 웹훅 경로 및 스레드 풀 사이즈 설정 추가
move-hoon df20e9a
[#315] feat(MemberRegistrationService): 회원가입 이벤트를 보내는 publisher 추가
move-hoon 472394a
[#315] feat(MemberRegisteredEvent): 회원가입 시 이벤트로 유저의 닉네임을 담는 DTO 추가
move-hoon e1c6b84
[#315] feat(MemberRegisteredEventListener): 회원가입 이벤트를 받아서 비동기로 슬랙 서비스…
move-hoon 503ac2b
[#315] feat: openFeign을 이용해 slack webhook URL로 요청을 보내는 기능 추가
move-hoon f5653b4
[#315] feat: 회원가입 유저 수를 측정하는 서비스 로직 구현
move-hoon 227660a
[#315] feat(GlobalAsyncExceptionHandler): 비동기 예외 핸들러 구현
move-hoon f358757
[#315] feat(AsyncThreadConfig): 스레드 풀 관련 Config 클래스 구현
move-hoon 97ec4c7
[#315] feat(ThreadPoolProperties): 스레드 coreSize, namePrefix를 관리하는 Con…
move-hoon 09ac955
[#315] chore(MemberRegisteredEventListener): 환영 메시지 변경
move-hoon 1a1dcc2
[#315] chore(GlobalAsyncExceptionHandler): import문 최적화
move-hoon 2ccdb4a
[#315] chore(JwtAuthenticationFilter): 토큰 없는 API 요청 로깅 레벨 변경
move-hoon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/main/java/com/beat/domain/member/application/dto/event/MemberRegisteredEvent.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.beat.domain.member.application.dto.event; | ||
|
|
||
| public record MemberRegisteredEvent( | ||
| String nickname | ||
| ) { | ||
| } |
2 changes: 1 addition & 1 deletion
2
...mber/dto/AccessTokenGenerateResponse.java → ...response/AccessTokenGenerateResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...main/member/dto/LoginSuccessResponse.java → ...on/dto/response/LoginSuccessResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
...omain/member/dto/MemberLoginResponse.java → ...ion/dto/response/MemberLoginResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
src/main/java/com/beat/global/common/config/AsyncThreadConfig.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.beat.global.common.config; | ||
|
|
||
| import java.util.concurrent.Executor; | ||
| import java.util.concurrent.ThreadPoolExecutor; | ||
|
|
||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.scheduling.annotation.AsyncConfigurer; | ||
| import org.springframework.scheduling.annotation.EnableAsync; | ||
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; | ||
| import org.springframework.security.concurrent.DelegatingSecurityContextExecutor; | ||
|
|
||
| import com.beat.global.common.handler.GlobalAsyncExceptionHandler; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Configuration | ||
| @EnableAsync | ||
| @RequiredArgsConstructor | ||
| public class AsyncThreadConfig implements AsyncConfigurer { | ||
| private final ThreadPoolProperties threadPoolProperties; | ||
|
|
||
| @Override | ||
| @Bean(name = "taskExecutor") | ||
| public Executor getAsyncExecutor() { | ||
| ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); | ||
| executor.setCorePoolSize(threadPoolProperties.getCoreSize()); | ||
| executor.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix()); | ||
| executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); | ||
| executor.initialize(); | ||
| return new DelegatingSecurityContextExecutor(executor.getThreadPoolExecutor()); | ||
| } | ||
|
|
||
| @Override | ||
| @Bean | ||
| public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { | ||
| return new GlobalAsyncExceptionHandler(); | ||
| } | ||
| } |
14 changes: 14 additions & 0 deletions
14
src/main/java/com/beat/global/common/config/ThreadPoolProperties.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.beat.global.common.config; | ||
|
|
||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @ConfigurationProperties(prefix = "thread-pool") | ||
| @RequiredArgsConstructor | ||
| @Getter | ||
| public class ThreadPoolProperties { | ||
| private final int coreSize; | ||
| private final String threadNamePrefix; | ||
| } |
28 changes: 28 additions & 0 deletions
28
src/main/java/com/beat/global/common/handler/GlobalAsyncExceptionHandler.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| package com.beat.global.common.handler; | ||
|
|
||
| import java.lang.reflect.Method; | ||
| import java.util.Arrays; | ||
|
|
||
| import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; | ||
| import org.springframework.lang.NonNull; | ||
| import org.springframework.lang.Nullable; | ||
|
|
||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| @Slf4j | ||
| public class GlobalAsyncExceptionHandler implements AsyncUncaughtExceptionHandler { | ||
|
|
||
| @Override | ||
| public void handleUncaughtException(@NonNull Throwable ex, @NonNull Method method, @Nullable Object... params) { | ||
| if (params == null) { | ||
| log.error("비동기 작업 중 예외 발생! Method: [{}], Params: [null], Exception: [{}]", | ||
| method.getName(), ex.getMessage(), ex); | ||
| return; | ||
| } | ||
|
|
||
| String paramValues = Arrays.toString(params); | ||
|
|
||
| log.error("비동기 작업 중 예외 발생! Method: [{}], Params: [{}], Exception: [{}]", | ||
| method.getName(), paramValues, ex.getMessage(), ex); | ||
| } | ||
| } |
15 changes: 15 additions & 0 deletions
15
src/main/java/com/beat/global/external/notification/slack/application/SlackClient.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.beat.global.external.notification.slack.application; | ||
|
|
||
| import org.springframework.cloud.openfeign.FeignClient; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| @FeignClient(name = "slackClient", url = "${slack.webhook.url}") | ||
| public interface SlackClient { | ||
|
|
||
| @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE) | ||
| void sendMessage(@RequestBody Map<String, String> payload); | ||
| } |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/beat/global/external/notification/slack/application/SlackService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.beat.global.external.notification.slack.application; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class SlackService { | ||
|
|
||
| private final SlackClient slackClient; | ||
|
|
||
| public void sendMessage(Map<String, String> payload) { | ||
| slackClient.sendMessage(payload); | ||
| } | ||
| } |
41 changes: 41 additions & 0 deletions
41
...java/com/beat/global/external/notification/slack/event/MemberRegisteredEventListener.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.beat.global.external.notification.slack.event; | ||
|
|
||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| import org.springframework.scheduling.annotation.Async; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.transaction.event.TransactionPhase; | ||
| import org.springframework.transaction.event.TransactionalEventListener; | ||
|
|
||
| import com.beat.domain.member.application.dto.event.MemberRegisteredEvent; | ||
| import com.beat.domain.member.port.in.MemberUseCase; | ||
| import com.beat.global.external.notification.slack.application.SlackService; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class MemberRegisteredEventListener { | ||
| private static final String TEXT_KEY = "text"; | ||
| private static final String WELCOME_MESSAGE = "번째 유저가 회원가입했띠예 🎉🎉 - "; | ||
| private static final String SLACK_TRANSFER_ERROR = "Slack 전송 실패"; | ||
|
|
||
| private final MemberUseCase memberUseCase; | ||
| private final SlackService slackService; | ||
|
|
||
| @Async("taskExecutor") | ||
| @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
| public void sendSlackNotification(MemberRegisteredEvent event) { | ||
| Map<String, String> payload = new HashMap<>(); | ||
| payload.put(TEXT_KEY, memberUseCase.countMembers() + WELCOME_MESSAGE + event.nickname()); | ||
|
|
||
| try { | ||
| slackService.sendMessage(payload); | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(SLACK_TRANSFER_ERROR); | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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.
🛠️ Refactor suggestion
Improve error handling for non-critical notification failures
The current implementation throws a
RuntimeExceptionwhen Slack notification fails, which might not be appropriate for a non-critical background task. Consider logging the error instead of throwing an exception, since a failed notification shouldn't impact the core application flow.try { slackService.sendMessage(payload); } catch (Exception e) { - throw new RuntimeException(SLACK_TRANSFER_ERROR); + log.error(SLACK_TRANSFER_ERROR, e); }📝 Committable suggestion
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.
Wouldn't just log.error() make it go to GlobalAsyncExceptionHandler and miss exception handling?
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.
@hoonyworld Actually, it would be the opposite - using
log.error()would prevent the exception from reaching theGlobalAsyncExceptionHandler.Here's how it works:
GlobalAsyncExceptionHandleronly handles uncaught exceptions from@AsyncmethodsRuntimeException, which becomes an uncaught exception that will be handled byGlobalAsyncExceptionHandlerlog.error(), you're fully handling the exception within the method, so nothing propagates toGlobalAsyncExceptionHandlerIf you want both logging and to have the exception handled by
GlobalAsyncExceptionHandler, you could do:But for non-critical notifications, simply logging the error is usually sufficient since you don't want notification failures to impact core functionality.
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.
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.
@hoonyworld Yes, you're absolutely right. Since your
GlobalAsyncExceptionHandleris already set up to log uncaught exceptions from@Asyncmethods, you can simply throw theRuntimeExceptionwithout adding a redundantlog.error()call.When you throw:
The exception will propagate to the
GlobalAsyncExceptionHandler, which will log the error with method name, parameters, and exception details.This approach has the advantage of providing consistent error logging across all your async operations. Just be aware that when throwing the exception, you're marking the entire async operation as failed, but since this is a non-critical notification feature running after transaction commit, that's a reasonable approach.