Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package site.holliverse.customer.application.usecase.log;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import site.holliverse.customer.integration.external.AdminLogFeaturesClient;

/**
* admin log-feature 별도 executor로 분리
*/
@Service
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

🛡️ [위반 규칙]: Infrastructure Layer Registration (Rule 56)

AdminLogFeatureDispatchService는 외부 시스템(AdminLogFeaturesClient)과의 통신을 처리하는 기술적 어댑터 역할을 수행합니다. 저장소 스타일 가이드 규칙 56번에 따라 어댑터 성격의 컴포넌트에는 @Service를 사용하지 않아야 하며, @Configuration 클래스에서 @Bean으로 등록해야 합니다. 또한, 아키텍처 경계를 명확히 하기 위해 해당 클래스를 infrastructure 패키지로 이동하는 것을 권장합니다.

References
  1. 어댑터에 @component(@service 포함)를 사용하지 않고 @configuration + @bean을 사용해야 함 (link)

@Profile("customer")
@RequiredArgsConstructor
public class AdminLogFeatureDispatchService {

private final AdminLogFeaturesClient adminLogFeaturesClient;

@Async("adminLogFeatureTaskExecutor")
public void dispatch(Long memberId, UserLogEventName eventName, String timestamp) {
adminLogFeaturesClient.sendLogFeature(memberId, eventName, timestamp);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.springframework.stereotype.Service;

import com.github.f4b6a3.tsid.Tsid;
import site.holliverse.customer.integration.external.AdminLogFeaturesClient;
import site.holliverse.customer.web.dto.log.UserLogRequest;
import site.holliverse.customer.error.CustomerErrorCode;
import site.holliverse.customer.error.CustomerException;
Expand All @@ -27,7 +26,7 @@ public class UserLogService {

private final KafkaTemplate<String, String> kafkaTemplate;
private final ObjectMapper objectMapper;
private final AdminLogFeaturesClient adminLogFeaturesClient;
private final AdminLogFeatureDispatchService adminLogFeatureDispatchService;
private final CustomerMetrics customerMetrics;

@Value("${app.topic.client-events}")
Expand Down Expand Up @@ -121,7 +120,7 @@ private void sendAdminTarget(Long memberId, UserLogRequest request) {
return;
}

adminLogFeaturesClient.sendLogFeature(
adminLogFeatureDispatchService.dispatch(
memberId,
eventName,
request.timestamp()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ public ThreadPoolTaskExecutor userLogTaskExecutor() {
return executor;
}

@Bean(name = "adminLogFeatureTaskExecutor")
public ThreadPoolTaskExecutor adminLogFeatureTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(16);
executor.setQueueCapacity(512);
executor.setThreadNamePrefix("admin-log-feature-");
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

🛡️ [개선 제안]: Executor Isolation & Reliability

adminLogFeatureTaskExecutorCallerRunsPolicy를 적용하면, Admin API 호출 지연 시 호출자 스레드(userLogTaskExecutor)가 직접 작업을 수행하게 되어 격리 효과가 사라집니다. Admin 로그 전송이 비즈니스 핵심 흐름에 지장을 주지 않아야 한다면, 시스템 전체의 안정성을 위해 DiscardPolicy를 적용하여 업스트림 스레드 풀을 보호하는 것이 좋습니다.

Suggested change
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.DiscardPolicy());

executor.initialize();
return executor;
}

@Bean(name = "recommendationTaskExecutor")
public ThreadPoolTaskExecutor recommendationTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ public CustomerMonitoringBinder(
MeterRegistry meterRegistry,
RecommendationPendingFutureRegistry pendingFutureRegistry,
@Qualifier("userLogTaskExecutor") ThreadPoolTaskExecutor userLogTaskExecutor,
@Qualifier("adminLogFeatureTaskExecutor") ThreadPoolTaskExecutor adminLogFeatureTaskExecutor,
@Qualifier("recommendationTaskExecutor") ThreadPoolTaskExecutor recommendationTaskExecutor
) {
Gauge.builder("holliverse.recommendation.pending.size", pendingFutureRegistry, RecommendationPendingFutureRegistry::size)
.description("Pending recommendation futures waiting for Kafka completion")
.register(meterRegistry);

bindExecutorMetrics(meterRegistry, "user-log", userLogTaskExecutor);
bindExecutorMetrics(meterRegistry, "admin-log-feature", adminLogFeatureTaskExecutor);
bindExecutorMetrics(meterRegistry, "recommendation-trigger", recommendationTaskExecutor);
}

Expand Down
Loading