Skip to content

Conversation

@move-hoon
Copy link
Member

@move-hoon move-hoon commented Mar 10, 2025

Related issue 🛠

Work Description ✏️

  • 회원가입 시 Slack으로 알림을 보내는 기능을 구현하였습니다.
  • 회원가입과 Slack 알림 기능을 같이 동기적으로 실행할 경우 slack 알림에 요청 대한 응답을 받은 후, 다음 회원가입 서비스 로직이 진행되므로 회원가입 응답속도에 영향을 미치게 됩니다.
  • 따라서 별도 스레드에서 비동기로 slack알림 기능을 처리할 수 있도록 구현하여 로그인 응답 속도에 영향을 미치지 않도록 했습니다

상세 작업

  • @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)을 이용해 커밋 이후에 이벤트 리스너가 작동할 수 있도록 구현하였습니다.
  • OpenFeign을 이용해 Slack Webhook URL로 회원가입 메시지를 보낼 수 있도록 구현하였습니다.
  • @async를 도입했기 때문에 쓰레드 풀 관련 설정을 추가했습니다. (AsyncThreadConfig)
  • 비동기 예외를 잡아줄 수 있는 GlobalAsyncExceptionHandler를 추가했습니다.

Trouble Shooting ⚽️

해결 시 참고 링크

트랜잭션 커밋 이전에 비동기 작업이 일어나는 문제

image

🚨 문제 상황

  • 기존에는 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)을 사용하지 않고, 일반적인 @async 방식으로 코드를 작성했습니다.
  • 회원가입 로직에서 memberRepository.save() 호출 후, @async 메서드가 실행되었기 때문에 트랜잭션 커밋 이후에 비동기 메서드가 실행될 것이라고 예상했습니다.
  • 그러나 실제 결과는 “2번째 유저가 가입했습니다”라는 예상과 달리, “1번째 유저가 가입했습니다”라는 잘못된 결과가 나왔습니다.

🔍 원인 분석

  • save()는 실제로 DB에 즉시 저장되지 않고, 영속성 컨텍스트에 저장된 후 flush() 또는 commit 시점에 DB에 반영됩니다.
  • 하지만 @async 메서드는 호출 즉시 별도의 스레드에서 비동기 작업을 수행하기 때문에, 회원가입 트랜잭션이 커밋되기 전에 count()가 호출되어 DB에 저장된 데이터가 1개뿐이라고 판단한 것입니다.

✅ 해결 방법

  • @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)을 도입하여, 트랜잭션 커밋 이후에 Slack 알림 메서드가 실행되도록 변경했습니다.

Related ScreenShot 📷

Uncompleted Tasks 😅

To Reviewers 📢

링크

  • ThreadPool 설정 기준, Spring EventListener 등에 대한 기능이 생소하실 것 같아 제가 SOPT Makers에서 비동기 이벤트 리스너 작업을 하고 문서화했던 문서링크를 첨부합니다!

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Integrated Slack notifications now deliver real-time alerts for member events.
    • Enhanced asynchronous processing and error handling improve overall responsiveness.
    • New method to count total members added to the MemberService.
    • Event publishing introduced in the member registration process to notify Slack.
  • Refactor

    • Streamlined member registration and login flows for a smoother experience.
    • Updated configuration and dependency management boost system stability and efficiency.
    • Reorganized package structure for better clarity and modularity.
    • Introduced new configuration options for thread pooling and Slack webhook integration.

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2025

Walkthrough

The changes introduce new dependencies in the build configuration and update package structures to reorganize DTOs and imports. An annotation for scanning configuration properties is added to the main application, and service methods are enhanced with a count operation and event publishing on member registration. New classes configure asynchronous execution and thread pools while a global asynchronous exception handler is implemented. Slack integration is added with a Feign client, a service for sending messages, and an event listener to process member registration events. Additionally, environment-specific configuration files are updated with Slack and thread pool settings.

Changes

File(s) Change Summary
build.gradle Added dependencies: Slack API client and Spring Boot Configuration Processor.
src/main/java/.../BeatApplication.java Added @ConfigurationPropertiesScan annotation to enable configuration properties scanning.
src/main/java/.../MemberApi.java, .../MemberController.java, .../AuthenticationService.java, .../SocialLoginService.java Updated import paths for DTOs to reflect package reorganization.
src/main/java/.../dto/response/AccessTokenGenerateResponse.java, .../LoginSuccessResponse.java, .../MemberLoginResponse.java Updated package declarations to the new application.dto.response structure.
src/main/java/.../MemberUseCase.java, .../MemberService.java Added new method countMembers() and annotated deleteUser with @Override.
src/main/java/.../MemberRegistrationService.java, .../dto/event/MemberRegisteredEvent.java Modified member registration to publish a MemberRegisteredEvent and introduced the event record.
src/main/java/.../common/config/AsyncThreadConfig.java, .../ThreadPoolProperties.java, .../common/handler/GlobalAsyncExceptionHandler.java Added asynchronous execution configuration with a custom thread pool and global exception handler.
src/main/java/.../external/notification/slack/application/SlackClient.java, .../SlackService.java, .../external/notification/slack/event/MemberRegisteredEventListener.java Introduced Slack integration with a Feign client, service for sending messages, and an asynchronous event listener for member registration notifications.
src/main/resources/application-dev.yml, src/main/resources/application-prod.yml Added configuration sections for Slack webhook URLs and thread pool settings.

Sequence Diagram(s)

sequenceDiagram
    participant MRS as MemberRegistrationService
    participant EP as EventPublisher
    participant MREL as MemberRegisteredEventListener
    participant SS as SlackService
    participant SC as SlackClient

    MRS->>EP: Publish MemberRegisteredEvent (with nickname)
    EP-->>MREL: Deliver event (after commit)
    MREL->>SS: Call sendSlackNotification(event)
    SS->>SC: sendMessage(payload)
    SC-->>SS: Acknowledge receipt
Loading

Poem

I'm just a rabbit with a happy heart,
Hopping through changes, a fresh new start.
Dependencies added and packages refined,
Asynchronous magic and Slack well-designed.
With each little hop, my joy won’t depart—
Code carrots and bounces, a masterpiece of art! 🐇


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a1dcc2 and 2ccdb4a.

📒 Files selected for processing (1)
  • src/main/java/com/beat/global/auth/jwt/filter/JwtAuthenticationFilter.java (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/com/beat/global/auth/jwt/filter/JwtAuthenticationFilter.java

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
src/main/java/com/beat/global/external/notification/slack/application/SlackClient.java (2)

1-15: Consider creating a dedicated DTO for Slack messages

The interface is well-defined using OpenFeign for the Slack webhook integration. However, using a generic Map<String, String> as the payload format might make the code less maintainable and type-safe.

Consider creating a dedicated DTO class that represents the Slack message format:

- void sendMessage(@RequestBody Map<String, String> payload);
+ void sendMessage(@RequestBody SlackMessageRequest payload);

And add a new class:

package com.beat.global.external.notification.slack.application.dto.request;

public record SlackMessageRequest(
    String text
    // Add other Slack message properties as needed
) {
    public static SlackMessageRequest of(String text) {
        return new SlackMessageRequest(text);
    }
}

This would make the code more readable and maintainable, with the added benefit of documentation through code.


10-10: Consider adding connection timeout configuration

When working with external services like Slack, it's a good practice to configure timeout settings to prevent long waits if the service is unresponsive.

- @FeignClient(name = "slackClient", url = "${slack.webhook.url}")
+ @FeignClient(
+    name = "slackClient", 
+    url = "${slack.webhook.url}",
+    configuration = SlackClientConfiguration.class
+ )

And create a configuration class:

package com.beat.global.external.notification.slack.application.config;

import feign.Request;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class SlackClientConfiguration {
    @Bean
    public Request.Options options() {
        return new Request.Options(
            5, TimeUnit.SECONDS, // connection timeout
            10, TimeUnit.SECONDS, // read timeout
            true // follow redirects
        );
    }
}
src/main/resources/application-prod.yml (1)

121-123: Consider thread pool sizing for production workloads.

While a core size of 2 may be sufficient initially, you should monitor thread pool utilization in production and adjust as needed based on actual workload.

Consider implementing metrics collection for your thread pool to help with capacity planning:

thread-pool:
  core-size: 2
  thread-name-prefix: executor-
+ max-size: 10
+ queue-capacity: 100
src/main/java/com/beat/global/external/notification/slack/application/SlackService.java (1)

15-17: Consider adding error handling.

The current implementation lacks error handling for cases where the Slack API might be unavailable or return errors.

Consider adding error handling to prevent issues with the Slack notification from affecting other operations:

public void sendMessage(Map<String, String> payload) {
-	slackClient.sendMessage(payload);
+	try {
+		slackClient.sendMessage(payload);
+	} catch (Exception e) {
+		// Log the error but don't propagate it
+		log.error("Failed to send Slack notification: {}", e.getMessage(), e);
+	}
}

Don't forget to add the Lombok @slf4j annotation to the class for logging.

src/main/java/com/beat/global/common/config/AsyncThreadConfig.java (1)

28-29: Consider setting additional executor properties

The current configuration only sets core pool size and thread name prefix. Consider adding:

  1. Maximum pool size (if different from core size)
  2. Queue capacity
  3. Keep-alive time for idle threads above core count

This would make the thread pool behavior more predictable under varying loads.

ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(threadPoolProperties.getCoreSize());
+ executor.setMaxPoolSize(threadPoolProperties.getMaxSize());
+ executor.setQueueCapacity(threadPoolProperties.getQueueCapacity());
+ executor.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
executor.setThreadNamePrefix(threadPoolProperties.getThreadNamePrefix());
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c396b6 and 09ac955.

📒 Files selected for processing (21)
  • build.gradle (1 hunks)
  • src/main/java/com/beat/BeatApplication.java (2 hunks)
  • src/main/java/com/beat/domain/member/api/MemberApi.java (1 hunks)
  • src/main/java/com/beat/domain/member/api/MemberController.java (1 hunks)
  • src/main/java/com/beat/domain/member/application/AuthenticationService.java (1 hunks)
  • src/main/java/com/beat/domain/member/application/MemberRegistrationService.java (4 hunks)
  • src/main/java/com/beat/domain/member/application/MemberService.java (1 hunks)
  • src/main/java/com/beat/domain/member/application/SocialLoginService.java (2 hunks)
  • src/main/java/com/beat/domain/member/application/dto/event/MemberRegisteredEvent.java (1 hunks)
  • src/main/java/com/beat/domain/member/application/dto/response/AccessTokenGenerateResponse.java (1 hunks)
  • src/main/java/com/beat/domain/member/application/dto/response/LoginSuccessResponse.java (1 hunks)
  • src/main/java/com/beat/domain/member/application/dto/response/MemberLoginResponse.java (1 hunks)
  • src/main/java/com/beat/domain/member/port/in/MemberUseCase.java (1 hunks)
  • src/main/java/com/beat/global/common/config/AsyncThreadConfig.java (1 hunks)
  • src/main/java/com/beat/global/common/config/ThreadPoolProperties.java (1 hunks)
  • src/main/java/com/beat/global/common/handler/GlobalAsyncExceptionHandler.java (1 hunks)
  • src/main/java/com/beat/global/external/notification/slack/application/SlackClient.java (1 hunks)
  • src/main/java/com/beat/global/external/notification/slack/application/SlackService.java (1 hunks)
  • src/main/java/com/beat/global/external/notification/slack/event/MemberRegisteredEventListener.java (1 hunks)
  • src/main/resources/application-dev.yml (1 hunks)
  • src/main/resources/application-prod.yml (1 hunks)
🔇 Additional comments (25)
src/main/java/com/beat/domain/member/port/in/MemberUseCase.java (1)

14-15: LGTM! Good addition of countMembers method.

This new method aligns well with the PR objective of providing Slack notifications, as it could be used to include the total member count in notifications upon new registrations.

src/main/java/com/beat/domain/member/application/dto/event/MemberRegisteredEvent.java (1)

1-6: Good implementation of event using Java record.

The MemberRegisteredEvent record is a clean, immutable data structure that perfectly fits the event-driven design for Slack notifications. This implementation aligns well with the PR objective of sending Slack notifications upon user registration.

src/main/java/com/beat/domain/member/application/dto/response/LoginSuccessResponse.java (1)

1-1: Appropriate package restructuring.

Moving this class to the application.dto.response package improves the organization of DTOs in the codebase, making the architecture more maintainable and clearer.

src/main/java/com/beat/domain/member/application/dto/response/AccessTokenGenerateResponse.java (1)

1-1: Good package structure reorganization.

Relocating this DTO to the application.dto.response package is consistent with the architectural improvements being made throughout the codebase.

src/main/java/com/beat/domain/member/application/dto/response/MemberLoginResponse.java (1)

1-1: LGTM: Well-structured package name

The updated package name follows a clean architecture approach, organizing DTOs by their layer (application) and purpose (response), which improves code organization and maintainability.

src/main/java/com/beat/BeatApplication.java (1)

6-6: Good addition of ConfigurationPropertiesScan

This annotation enables automatic detection of @ConfigurationProperties classes throughout the application, which will help with your Slack webhook configuration and thread pool settings. This is the correct approach for externalizing configuration in Spring applications.

Also applies to: 18-18

build.gradle (1)

109-114: LGTM: Appropriate dependencies for Slack integration

The Slack API client and Spring Boot Configuration Processor are appropriate dependencies for implementing the Slack notification feature.

src/main/java/com/beat/domain/member/application/SocialLoginService.java (1)

5-5: Import path update looks good.

The updated import path follows a more structured organization pattern, moving the DTO to a dedicated response package within the application layer.

src/main/java/com/beat/domain/member/api/MemberApi.java (1)

8-9: Import path restructuring approved.

These import changes maintain consistency with the overall package reorganization, properly relocating response DTOs to the application-specific package.

src/main/resources/application-dev.yml (2)

118-120: Slack webhook configuration looks good.

The Slack webhook URL is properly configured using an environment variable which is a good security practice.


122-124: Thread pool configuration is appropriate for development.

The thread pool configuration with a core size of 2 is suitable for a development environment. The thread name prefix will help with identifying these threads in logs and thread dumps.

Consider monitoring thread pool metrics in production to ensure the core size is appropriate for your workload. You might need to adjust it based on performance testing results.

src/main/resources/application-prod.yml (1)

117-119: Slack webhook configuration is properly set.

Using environment variables for the Slack webhook URL in production is a good security practice.

src/main/java/com/beat/global/external/notification/slack/application/SlackService.java (1)

9-11: Service setup looks good.

The service is properly annotated and follows the Spring dependency injection pattern.

src/main/java/com/beat/global/common/config/ThreadPoolProperties.java (1)

1-14: Well-structured configuration properties class

This class is correctly implemented to bind external configuration properties with the "thread-pool" prefix. The use of final fields ensures immutability, and Lombok annotations reduce boilerplate code.

The properties will be used to configure thread pools for asynchronous operations, which aligns well with the PR objective of implementing asynchronous Slack notifications.

src/main/java/com/beat/domain/member/api/MemberController.java (1)

15-17: Import path reorganization looks good

The imports have been updated to reflect the new package structure for response DTOs, which improves code organization.

src/main/java/com/beat/domain/member/application/MemberRegistrationService.java (4)

3-3: New event import for async processing

Importing the MemberRegisteredEvent class for event-driven processing of member registration.


14-14: ApplicationEventPublisher import for event publishing

This import enables the service to publish events throughout the application.


23-23: Event publisher dependency injection

The ApplicationEventPublisher is properly injected as a final field through the constructor using Lombok's @requiredargsconstructor.


49-50: Event publication after member registration

The implementation correctly publishes the MemberRegisteredEvent after the member is successfully registered. This aligns with the PR objective of implementing asynchronous Slack notifications without affecting the registration response time.

Note that the event is published within the transaction. Since you're using TransactionPhase.AFTER_COMMIT in your listener (as mentioned in the PR objectives), this ensures the notification is sent only after successful transaction completion.

src/main/java/com/beat/domain/member/application/MemberService.java (2)

46-46: Added @OverRide annotation

Explicitly marking the method as implementing an interface method improves code clarity.


55-59: New method to count members

This method is properly implemented with a read-only transaction annotation, which is appropriate for a query method. It will be used in the Slack notification to report the total member count after a new registration.

src/main/java/com/beat/domain/member/application/AuthenticationService.java (1)

3-4: Good DTO package restructuring

The import path changes reflect a logical reorganization of DTOs into a more structured package path. This aligns with clean architecture principles by better separating response DTOs in a dedicated package.

src/main/java/com/beat/global/external/notification/slack/event/MemberRegisteredEventListener.java (1)

29-31: Excellent async implementation with transaction awareness

The combination of @Async("taskExecutor") with @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) ensures that:

  1. The notification doesn't block the main registration flow
  2. The notification only executes after the transaction is successfully committed
  3. The notification won't execute if the transaction fails

This addresses the issue mentioned in the PR objectives where asynchronous tasks were executing before transaction commit.

src/main/java/com/beat/global/common/handler/GlobalAsyncExceptionHandler.java (1)

14-26: Well-implemented async exception handler with comprehensive logging

The exception handler properly logs the method name, parameters, and exception details, making it easier to troubleshoot async execution failures. The null check for parameters is a good defensive programming practice.

src/main/java/com/beat/global/common/config/AsyncThreadConfig.java (1)

24-33: Well-configured thread pool with security context preservation

The async executor configuration is well implemented with:

  1. Configurable core pool size from properties
  2. Descriptive thread naming for easier debugging
  3. A CallerRunsPolicy for handling thread pool saturation
  4. Security context preservation using DelegatingSecurityContextExecutor

The CallerRunsPolicy is particularly good as it provides backpressure by making the calling thread execute the task when the queue is full, preventing resource exhaustion.

Comment on lines +35 to +39
try {
slackService.sendMessage(payload);
} catch (Exception e) {
throw new RuntimeException(SLACK_TRANSFER_ERROR);
}
Copy link

@coderabbitai coderabbitai bot Mar 10, 2025

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 RuntimeException when 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

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
slackService.sendMessage(payload);
} catch (Exception e) {
throw new RuntimeException(SLACK_TRANSFER_ERROR);
}
try {
slackService.sendMessage(payload);
} catch (Exception e) {
log.error(SLACK_TRANSFER_ERROR, e);
}

Copy link
Member Author

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?

Copy link

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 the GlobalAsyncExceptionHandler.

Here's how it works:

  1. The GlobalAsyncExceptionHandler only handles uncaught exceptions from @Async methods
  2. In your current code, you're catching the exception but then throwing a new RuntimeException, which becomes an uncaught exception that will be handled by GlobalAsyncExceptionHandler
  3. If you replace it with log.error(), you're fully handling the exception within the method, so nothing propagates to GlobalAsyncExceptionHandler

If you want both logging and to have the exception handled by GlobalAsyncExceptionHandler, you could do:

try {
    slackService.sendMessage(payload);
} catch (Exception e) {
    log.error(SLACK_TRANSFER_ERROR, e);
    throw new RuntimeException(SLACK_TRANSFER_ERROR, e);
}

But for non-critical notifications, simply logging the error is usually sufficient since you don't want notification failures to impact core functionality.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
	}
}
  • 'GlobalAsyncExceptionHandler' is logging errors, so can I just throw RuntimeException without using log.error?

Copy link

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 GlobalAsyncExceptionHandler is already set up to log uncaught exceptions from @Async methods, you can simply throw the RuntimeException without adding a redundant log.error() call.

When you throw:

throw new RuntimeException(SLACK_TRANSFER_ERROR);

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.

coderabbitai bot added a commit that referenced this pull request Mar 10, 2025
Docstrings generation was requested by @hoonyworld.

* #316 (comment)

The following files were modified:

* `src/main/java/com/beat/BeatApplication.java`
* `src/main/java/com/beat/domain/member/application/MemberRegistrationService.java`
* `src/main/java/com/beat/domain/member/application/MemberService.java`
* `src/main/java/com/beat/domain/member/application/SocialLoginService.java`
* `src/main/java/com/beat/domain/member/port/in/MemberUseCase.java`
* `src/main/java/com/beat/global/common/config/AsyncThreadConfig.java`
* `src/main/java/com/beat/global/common/handler/GlobalAsyncExceptionHandler.java`
* `src/main/java/com/beat/global/external/notification/slack/application/SlackClient.java`
* `src/main/java/com/beat/global/external/notification/slack/application/SlackService.java`
* `src/main/java/com/beat/global/external/notification/slack/event/MemberRegisteredEventListener.java`
@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 2025

Note

Generated docstrings for this pull request at #317

Copy link
Collaborator

@hyerinhwang-sailin hyerinhwang-sailin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

,회원가입 알림 비동기 처리를 위해 고민하신 부분이 인상깊네요! 문서까지 공유해주셔서 잘 이해됐습니다~

Copy link
Collaborator

@hyerinhwang-sailin hyerinhwang-sailin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm😊

@move-hoon move-hoon merged commit ae694ef into develop Mar 10, 2025
2 checks passed
@move-hoon move-hoon deleted the feat/#315 branch July 25, 2025 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 회원가입 시 slack 알림 기능 추가

3 participants