
이번에 구현하려던 기능은 유저가 책을 읽음(user_book_read 테이블에 추가) → ML 서버에 유저 벡터 생성 API 호출 → user_embedding 테이블 갱신 의 흐름이었다.
이 기능은 두 가지 경우에 동작해야 한다.
회원가입 시 설문 작성 → 선호 도서(user_book_read) 저장
도서 스크랩 시 상태가 READ로 변경 → user_book_read 저장
이벤트 기반 비동기 처리
[UserPreferenceService or BookScrapService]
↓ (user_book_read 저장)
이벤트 발행(UserBookReadAddedEvent)
↓
[UserVectorGenerateListener]
↓ (비동기 호출)
ML 서버 /api/users/{id}/generate-vector 호출
@Value
public class UserBookReadAddedEvent {
Long userId;
}
1) 회원가입 설문 저장 로직(UserPreferenceService)
boolean anyReadInserted = false;
if (dto.getPreferredBookId() != null) {
for (Integer bookId : dto.getPreferredBookId()) {
if (!userBookReadRepository.existsByUser_UserIdAndBook_BookId(userId, bookId)) {
userBookReadRepository.save(...);
anyReadInserted = true;
}
}
}
if (anyReadInserted) {
eventPublisher.publishEvent(new UserBookReadAddedEvent(userId));
}
2) 도서 스크랩 로직(BookScrapService)
boolean readInsertedThisTx = false;
if ("READ".equals(status)) {
if (!userBookReadRepository.existsByUser_UserIdAndBook_BookId(userId, bookId)) {
userBookReadRepository.save(...);
readInsertedThisTx = true;
}
}
if ("READ".equals(status) && readInsertedThisTx && !isReadBefore) {
eventPublisher.publishEvent(new UserBookReadAddedEvent(userId));
}
@Component
@RequiredArgsConstructor
public class UserVectorGenerateListener {
private final RestTemplate restTemplate;
@Value("${ml.server.url}")
private String mlBaseUrl;
@Async("vectorTaskExecutor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onUserBookReadAdded(UserBookReadAddedEvent event) {
String url = mlBaseUrl + "/api/users/" + event.getUserId() + "/generate-vector";
restTemplate.postForEntity(url, null, Void.class);
}
}
실행 시 다음 오류 발생:
java.lang.NoClassDefFoundError: org/apache/hc/client5/http/classic/HttpClient
java.lang.NoClassDefFoundError: org/apache/hc/client5/http/ssl/TlsSocketStrategy
원인:
Apache 의존성 추가 대신, JDK 기본 HttpClient를 쓰는 방식으로 변경.
이러면 의존성 없이 깔끔하게 동작.
수정 코드
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
HttpClient jdkClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(2))
.build();
JdkClientHttpRequestFactory factory = new JdkClientHttpRequestFactory(jdkClient);
factory.setReadTimeout(Duration.ofSeconds(10));
return builder.requestFactory(() -> factory).build();
}
application.yml
spring:
http:
client:
factory: jdk
Gradle
Apache HttpClient 관련 의존성 전부 제거.
유저가 책을 읽음 → ML 서버로 비동기 벡터 생성 요청 → user_embedding 자동 갱신.
Apache HttpClient 관련 NoClassDefFoundError 사라짐.
의존성 가벼워지고, Spring Boot 3.4.x와 호환성 문제도 해결.
동기와 비동기 차이를 공부해보고 싶어 밑 벨로그 글에 정리해놨다
동기와 비동기 차이