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
2 changes: 2 additions & 0 deletions profile-search/src/main/java/com/task/ProfileApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
@EnableJpaAuditing
@EnableCaching
Expand Down
45 changes: 45 additions & 0 deletions profile-search/src/main/java/com/task/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.task.config;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Executor;
import lombok.extern.slf4j.Slf4j;
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.concurrent.ThreadPoolTaskExecutor;

@Configuration
@Slf4j
public class AsyncConfig implements AsyncConfigurer {

@Bean("TaskExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int cpuCoreCount = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(cpuCoreCount);
executor.setMaxPoolSize(cpuCoreCount * 2);
executor.setQueueCapacity(10);
executor.setKeepAliveSeconds(60);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setThreadNamePrefix("Task-");
executor.initialize();
return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}

private class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("Failed to execute {}", ex.getMessage());
Arrays.asList(params).forEach(param -> log.error("parameter value = {}", param));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class ProfileController {
// 상세 조회
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<ProfileResponse>> getById(@PathVariable("id") Long id){
ProfileResponse response = profileApplicationService.getById(id);
ProfileResponse response = profileApplicationService.getProfileAndSendEvent(id);
return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.ok(response));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ public class ProfileViewStatEntity {
@JoinColumn(name = "profile_id")
private ProfileEntity profileEntity;

@CreatedDate
@Column(name = "created_at")
private LocalDateTime createdAt;

public ProfileViewStatEntity(ProfileEntity profileEntity) {
public ProfileViewStatEntity(ProfileEntity profileEntity, LocalDateTime timeStamp) {
this.profileEntity = profileEntity;
this.createdAt = timeStamp;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.task.service;

import com.task.ApiException;
import com.task.ErrorType;
import com.task.PageResult;
import com.task.controller.response.ProfileResponse;
import com.task.infrastructure.profile.ProfileEntity;
import com.task.infrastructure.profile_stat.ProfileViewStatEntity;
import com.task.service.profile.ProfileQueryService;
import com.task.service.profile_stat.ProfileViewStatCommandService;
import com.task.service.profile.event.ProfileQueryEvent;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

Expand All @@ -19,26 +17,16 @@
public class ProfileApplicationService {

private final ProfileQueryService profileQueryService;
private final ProfileViewStatCommandService profileViewStatCommandService;
private final ApplicationEventPublisher eventPublisher;

@Cacheable(value = "profile", key = "#id")
public ProfileResponse getById(Long id) {
ProfileResponse response = profileQueryService.getById(id);
ProfileViewStatEntity viewStatEntity = new ProfileViewStatEntity(new ProfileEntity(
response.getId(),
response.getName()));
profileViewStatCommandService.save(viewStatEntity); // 이벤트로 빼기
public ProfileResponse getProfileAndSendEvent(Long id) {
ProfileResponse response = profileQueryService.getById(id); // 캐시에서 조회
eventPublisher.publishEvent(new ProfileQueryEvent(id, response.getName(), LocalDateTime.now())); // 향후 kafka로 변경
return response;
}

@Cacheable(cacheNames = "profiles", value = "profiles", key = "'page_' + #pageable.getOffset() + "
+ "'_size_' + #pageable.getPageSize() + '_sort_' + #pageable.sort.toString()")
public PageResult<ProfileResponse> getAllByCondition(Pageable pageable) {
return profileQueryService.getAllByCondition(pageable);
}

@Cacheable(key = "hello")
public String test() {
return "hello";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.task.infrastructure.profile.ProfileEntity;
import com.task.infrastructure.profile.ProfileRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
Expand All @@ -18,6 +19,7 @@ public class ProfileQueryServiceImpl implements ProfileQueryService {

private final ProfileRepository profileRepository;

@Cacheable(value = "profile", key = "#id")
@Override
public ProfileResponse getById(Long id) {
return profileRepository.searchById(id)
Expand All @@ -26,6 +28,9 @@ public ProfileResponse getById(Long id) {
, HttpStatus.NOT_FOUND));
}

@Cacheable(cacheNames = "profiles", value = "profiles", key =
"'page_' + #pageable.getOffset() + "
+ "'_size_' + #pageable.getPageSize() + '_sort_' + #pageable.sort.toString()")
@Override
public PageResult<ProfileResponse> getAllByCondition(Pageable pageable) {
return profileRepository.getAllByCondition(pageable);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.task.service.profile.event;

import java.time.LocalDateTime;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;


public record ProfileQueryEvent(Long profileId, String name, LocalDateTime timeStamp) { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.task.service.profile.event;

import com.task.infrastructure.profile.ProfileEntity;
import com.task.infrastructure.profile_stat.ProfileViewStatEntity;
import com.task.service.profile_stat.ProfileViewStatCommandService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class ProfileQueryEventHandler {

private final ProfileViewStatCommandService profileViewStatCommandService;

@Async
@EventListener
public void handleProfileQueryEvent(ProfileQueryEvent event) throws InterruptedException {
log.info("[ProfileQueryEventHandler] handleEvent : {}", event);
ProfileEntity profile = new ProfileEntity(event.profileId(), event.name());
ProfileViewStatEntity profileViewStat = new ProfileViewStatEntity(profile, event.timeStamp());
profileViewStatCommandService.save(profileViewStat);
}
}