Skip to content

Commit d597bfc

Browse files
authored
Merge pull request #31 from CarToi/dev
[Refactor/embedding qpm] 기존 POST 요청 로직 GET 요청 추가 분리 + 캐싱 적용
2 parents 71e4594 + f0e7f7a commit d597bfc

File tree

23 files changed

+203
-34
lines changed

23 files changed

+203
-34
lines changed

src/main/java/org/jun/saemangeum/consume/controller/SurveyController.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,30 @@ public class SurveyController {
1919
private final SurveyRecommendationService surveyRecommendationService;
2020

2121
/**
22-
* 사용자 설문 결과 추천 응답 데이터 리스트 반환
22+
* 사용자 설문 결과 추천 응답 데이터 리스트 생성
2323
*/
2424
@PostMapping("/recommendation")
25-
public List<RecommendationResponse> createSurvey(@RequestBody SurveyCreateRequest request) {
26-
log.info("{} - 데이터 소비 요청응답", Thread.currentThread().getName());
27-
return surveyRecommendationService.createRecommendationsBySurvey(request);
25+
public void createSurvey(@RequestBody SurveyCreateRequest request) {
26+
log.info("{} - 데이터 소비 POST 요청", Thread.currentThread().getName());
27+
surveyRecommendationService.createRecommendationsBySurvey(request);
2828
}
2929

30+
/**
31+
* 사용자 설문 결과 추천 응답 데이터 리스트 반환
32+
*/
33+
@GetMapping
34+
public List<RecommendationResponse> getSurveyResults(@RequestParam("clientId") String clientId) {
35+
log.info("{} - 데이터 조회 GET 요청응답", Thread.currentThread().getName());
36+
return surveyRecommendationService.getSurveyRecommendationResults(clientId);
37+
}
38+
39+
3040
/**
3141
* 사용자 설문 결과 만족도 반영 요청
3242
*/
3343
@PatchMapping("/update")
3444
public void updateSurvey(@RequestBody SurveyUpdateRequest request) {
35-
log.info("{} - 데이터 소비 업데이트", Thread.currentThread().getName());
45+
log.info("{} - 데이터 업데이트 PATCH 요청", Thread.currentThread().getName());
3646
surveyRecommendationService.updateSurvey(request);
3747
}
3848
}

src/main/java/org/jun/saemangeum/consume/domain/entity/RecommendationLog.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ public class RecommendationLog {
2121
@Column(name = "content_title")
2222
private String contentTitle;
2323

24-
@Column(name = "survey_id")
25-
private Long surveyId;
24+
@Column(name = "survey_client_id")
25+
private String surveyClientId;
2626

2727
public RecommendationLog(IContent content, Survey survey) {
2828
this.contentTitle = content.getTitle();
29-
this.surveyId = survey.getId();
29+
this.surveyClientId = survey.getClientId();
3030
}
3131
}

src/main/java/org/jun/saemangeum/consume/domain/entity/Survey.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class Survey {
1919
@GeneratedValue(strategy = GenerationType.IDENTITY)
2020
private Long id;
2121

22-
@Column(name = "client_id")
22+
@Column(name = "client_id", unique = true)
2323
private String clientId; // UUID 클라이언트 id
2424

2525
// 설문 응답
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
package org.jun.saemangeum.consume.repository.swap;
22

3+
import java.util.List;
34
import org.jun.saemangeum.consume.domain.swap.ContentView;
45
import org.springframework.data.jpa.repository.JpaRepository;
6+
import org.springframework.data.jpa.repository.Query;
7+
import org.springframework.data.repository.query.Param;
58
import org.springframework.stereotype.Repository;
69

710
@Repository
811
public interface ContentViewRepository extends JpaRepository<ContentView, Long> {
12+
@Query(value = """
13+
SELECT cv.*
14+
FROM recommendation_logs rl
15+
JOIN contents_view cv ON rl.content_title = cv.title
16+
WHERE rl.survey_client_id = :clientId
17+
""", nativeQuery = true)
18+
List<ContentView> findRecommendedContentViewsByClientId(@Param("clientId") String clientId);
919
}

src/main/java/org/jun/saemangeum/consume/service/application/SurveyRecommendationService.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package org.jun.saemangeum.consume.service.application;
22

33
import lombok.RequiredArgsConstructor;
4-
import org.jun.saemangeum.consume.domain.dto.Coordinate;
54
import org.jun.saemangeum.consume.domain.dto.RecommendationResponse;
65
import org.jun.saemangeum.consume.domain.dto.SurveyCreateRequest;
76
import org.jun.saemangeum.consume.domain.dto.SurveyUpdateRequest;
@@ -11,8 +10,11 @@
1110
import org.jun.saemangeum.consume.service.domain.SurveyService;
1211
import org.jun.saemangeum.consume.service.strategy.StrategyContextHolder;
1312
import org.jun.saemangeum.consume.util.CoordinateCalculator;
13+
import org.jun.saemangeum.global.cache.CacheNames;
1414
import org.jun.saemangeum.global.domain.IContent;
15+
import org.jun.saemangeum.global.exception.ClientIdException;
1516
import org.jun.saemangeum.global.exception.SatisfactionsException;
17+
import org.springframework.cache.annotation.Cacheable;
1618
import org.springframework.stereotype.Service;
1719
import org.springframework.transaction.annotation.Transactional;
1820

@@ -29,20 +31,37 @@ public class SurveyRecommendationService {
2931
* 사용자 설문응답 문자열을 일괄 묶어 임베딩 벡터 처리 후, 로그 확보 + 추천 리스트 반환
3032
*/
3133
public List<RecommendationResponse> createRecommendationsBySurvey(SurveyCreateRequest request) {
34+
// 클라이언트 이슈 : 페이지 리랜더링 시 요청이 똑같이 들어오고 있음
35+
// 서버에서 어떻게 중복 요청을 막을 수 있을지?
36+
// 코드 로직 내에서 요청 자체를 소모시켜버리는 방안으로 생각해보기
37+
// 별개의 GET 요청을 만들자?
38+
if (surveyService.isExistedClientId(request.clientId()))
39+
throw new ClientIdException("해당 클라이언트 ID는 이미 존재합니다. 관리자에게 문의하거나 다시 시도해주세요.");
40+
3241
String age = request.age() > 30 ? "늙은" : "젊은";
3342
String awareness = ""; // request.resident()
3443
String text = request.gender() + " " + age + " " + awareness
3544
+ request.city() + " " + request.mood() + " " + request.want();
3645

3746
// 전략 패턴 적용
38-
List<? extends IContent> contents = StrategyContextHolder.executeStrategy(text);
47+
List<? extends IContent> contents = StrategyContextHolder.executePostStrategy(text);
3948
Survey survey = surveyService.save(Survey.create(request));
4049

4150
List<RecommendationLog> recommendationLogs = contents.stream()
4251
.map(e -> new RecommendationLog(e, survey)).toList();
4352
recommendationLogService.saveALl(recommendationLogs);
4453

45-
Coordinate coordinate = new Coordinate(0.1, 0.1);
54+
return contents.stream().map(IContent::to)
55+
.toList();
56+
}
57+
58+
/**
59+
* 임시 GET 요청 처리 서비스 로직
60+
*/
61+
// 너도 캐싱 대상이 되겠는데?
62+
@Cacheable(cacheNames = CacheNames.RESULTS, key = "#clientId")
63+
public List<RecommendationResponse> getSurveyRecommendationResults(String clientId) {
64+
List<? extends IContent> contents = StrategyContextHolder.executeGetStrategy(clientId);
4665

4766
return contents.stream()
4867
.map(IContent::to)

src/main/java/org/jun/saemangeum/consume/service/domain/RecommendationLogService.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,4 @@ public class RecommendationLogService {
1919
public void saveALl(List<RecommendationLog> recommendationLogs) {
2020
recommendationLogRepository.saveAll(recommendationLogs);
2121
}
22-
23-
// 너도 캐시 적용이 될까?
24-
public List<Long> getRecommendationLogIdsJoinSurveys(AverageRequest request) {
25-
return recommendationLogRepository.findContentIdsBySurveyConditions(
26-
request.age(), request.gender(), request.city(), request.want(), request.mood());
27-
}
2822
}

src/main/java/org/jun/saemangeum/consume/service/domain/SurveyService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ public Survey save(Survey survey) {
1818
return surveyRepository.save(survey);
1919
}
2020

21+
public boolean isExistedClientId(String clientId) {
22+
return surveyRepository.findByClientId(clientId).isPresent();
23+
}
24+
2125
public Survey findByClientId(String clientId) {
2226
return surveyRepository.findByClientId(clientId).orElseThrow(
2327
// 커스텀 예외로 전환 + 전역 예외 핸들러 처리

src/main/java/org/jun/saemangeum/consume/service/strategy/EmbeddingVectorStrategy.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66

77
public interface EmbeddingVectorStrategy {
88
List<? extends IContent> calculateSimilarity(String text);
9+
List<? extends IContent> getContentsByClientId(String clientId);
910
}

src/main/java/org/jun/saemangeum/consume/service/strategy/StrategyContextHolder.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,21 @@ public static void setStrategy(EmbeddingVectorStrategy strategy) {
1818
}
1919
}
2020

21-
public static List<? extends IContent> executeStrategy(String text) {
21+
public static List<? extends IContent> executePostStrategy(String text) {
2222
lock.readLock().lock();
2323
try {
2424
return currentStrategy.calculateSimilarity(text);
2525
} finally {
2626
lock.readLock().unlock();
2727
}
2828
}
29+
30+
public static List<? extends IContent> executeGetStrategy(String clientId) {
31+
lock.readLock().lock();
32+
try {
33+
return currentStrategy.getContentsByClientId(clientId);
34+
} finally {
35+
lock.readLock().unlock();
36+
}
37+
}
2938
}

src/main/java/org/jun/saemangeum/consume/service/strategy/TableEmbeddingVectorStrategy.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.jun.saemangeum.global.domain.Content;
55
import org.jun.saemangeum.global.domain.IContent;
66
import org.jun.saemangeum.global.domain.Vector;
7+
import org.jun.saemangeum.global.service.ContentService;
78
import org.jun.saemangeum.global.service.VectorService;
89
import org.jun.saemangeum.pipeline.application.util.VectorCalculator;
910
import org.jun.saemangeum.pipeline.infrastructure.api.VectorClient;
@@ -22,13 +23,14 @@ public class TableEmbeddingVectorStrategy implements EmbeddingVectorStrategy {
2223

2324
private final VectorClient vectorClient;
2425
private final VectorService vectorService;
26+
private final ContentService contentService;
2527

2628
@Override
2729
public List<? extends IContent> calculateSimilarity(String text) {
28-
EmbeddingResponse response = vectorClient.get(text);
30+
EmbeddingResponse response = vectorClient.getWithCache(text);
2931
float[] requestVec = VectorCalculator.addNoise(response.result().embedding());
3032

31-
List<Vector> vectors = vectorService.getVectors(); // 이거 캐싱 대상이겠는데?
33+
List<Vector> vectors = vectorService.getVectors(); // 이놈도 캐시 추가
3234
PriorityQueue<TableEmbeddingVectorStrategy.ContentSimilarity> pq = new PriorityQueue<>();
3335

3436
for (Vector vec : vectors) {
@@ -57,6 +59,12 @@ private float[] byteToFloat(Vector vector) {
5759
return floats;
5860
}
5961

62+
// 클라이언트 ID 기반 사용자 설문 응답 조회하기
63+
@Override
64+
public List<? extends IContent> getContentsByClientId(String clientId) {
65+
return contentService.getContentsByClientId(clientId);
66+
}
67+
6068
// 유사도 내부 클래스
6169
record ContentSimilarity(Content content, double similarity)
6270
implements Comparable<TableEmbeddingVectorStrategy.ContentSimilarity> {

0 commit comments

Comments
 (0)