Skip to content

Commit 6a936b4

Browse files
authored
feat: implement tag recommendation caching (#62)
2 parents 0ec082a + 25ca357 commit 6a936b4

File tree

35 files changed

+422
-164
lines changed

35 files changed

+422
-164
lines changed

noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/RecommendApi.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ package noweekend.client.mcp.recommend
33
import noweekend.client.mcp.recommend.model.SandwichRequest
44
import noweekend.client.mcp.recommend.model.SandwichResponse
55
import noweekend.client.mcp.recommend.model.TagRequest
6-
import noweekend.client.mcp.recommend.model.TagResponse
76
import noweekend.client.mcp.recommend.model.WeatherRequest
7+
import noweekend.core.domain.tag.TagRecommendation
88
import noweekend.core.domain.weather.WeatherRecommendation
99
import org.springframework.cloud.openfeign.FeignClient
1010
import org.springframework.http.MediaType
@@ -29,14 +29,14 @@ interface RecommendApi {
2929
consumes = [MediaType.APPLICATION_JSON_VALUE],
3030
method = [RequestMethod.POST],
3131
)
32-
fun getTag(@RequestBody request: TagRequest): List<TagResponse>
32+
fun getTag(@RequestBody request: TagRequest): List<TagRecommendation>
3333

3434
@RequestMapping(
3535
value = ["/getTagOnlyNew"],
3636
consumes = [MediaType.APPLICATION_JSON_VALUE],
3737
method = [RequestMethod.POST],
3838
)
39-
fun getTagOnlyNew(@RequestBody request: TagRequest): List<TagResponse>
39+
fun getTagOnlyNew(@RequestBody request: TagRequest): List<TagRecommendation>
4040

4141
@RequestMapping(
4242
value = ["/getSandwich"],

noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/RecommendClient.kt

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ package noweekend.client.mcp.recommend
33
import feign.FeignException
44
import noweekend.client.mcp.recommend.model.SandwichRequest
55
import noweekend.client.mcp.recommend.model.SandwichResponse
6-
import noweekend.client.mcp.recommend.model.TagApiResponses
76
import noweekend.client.mcp.recommend.model.TagRequest
8-
import noweekend.client.mcp.recommend.model.TagResponse
97
import noweekend.client.mcp.recommend.model.WeatherRequest
8+
import noweekend.client.mcp.recommend.model.toRequestType
9+
import noweekend.core.domain.tag.TagRecommendation
10+
import noweekend.core.domain.tag.TagRecommendations
1011
import noweekend.core.domain.tag.UserTags
1112
import noweekend.core.domain.weather.WeatherRecommendation
1213
import org.slf4j.LoggerFactory
@@ -30,9 +31,10 @@ class RecommendClient(
3031
}
3132
}
3233

33-
fun getRecommend(request: UserTags): TagApiResponses? {
34+
fun getRecommend(request: UserTags): TagRecommendations? {
35+
val requestForApi = TagRequest(request.toRequestType())
3436
val response = try {
35-
api.getTag(TagRequest(request))
37+
api.getTag(requestForApi)
3638
} catch (e: FeignException) {
3739
log.warn("[getRecommend] FeignException occurred. Returning null. msg=${e.message}")
3840
return null
@@ -46,16 +48,17 @@ class RecommendClient(
4648
return null
4749
}
4850

49-
return TagApiResponses(
50-
firstRecommendTag = TagResponse(response[0].content),
51-
secondRecommendTag = TagResponse(response[1].content),
52-
thirdRecommendTag = TagResponse(response[2].content),
51+
return TagRecommendations(
52+
firstRecommendTag = TagRecommendation(response[0].content),
53+
secondRecommendTag = TagRecommendation(response[1].content),
54+
thirdRecommendTag = TagRecommendation(response[2].content),
5355
)
5456
}
5557

56-
fun getOnlyNewRecommend(request: UserTags): TagApiResponses? {
58+
fun getOnlyNewRecommend(request: UserTags): TagRecommendations? {
59+
val requestForApi = TagRequest(request.toRequestType())
5760
val response = try {
58-
api.getTagOnlyNew(TagRequest(request))
61+
api.getTagOnlyNew(requestForApi)
5962
} catch (e: FeignException) {
6063
log.warn("[getOnlyNewRecommend] FeignException occurred. Returning null. msg=${e.message}")
6164
return null
@@ -69,10 +72,10 @@ class RecommendClient(
6972
return null
7073
}
7174

72-
return TagApiResponses(
73-
firstRecommendTag = TagResponse(response[0].content),
74-
secondRecommendTag = TagResponse(response[1].content),
75-
thirdRecommendTag = TagResponse(response[2].content),
75+
return TagRecommendations(
76+
firstRecommendTag = TagRecommendation(response[0].content),
77+
secondRecommendTag = TagRecommendation(response[1].content),
78+
thirdRecommendTag = TagRecommendation(response[2].content),
7679
)
7780
}
7881

noweekend-clients/client-mcp/src/main/kotlin/noweekend/client/mcp/recommend/model/Tag.kt

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package noweekend.client.mcp.recommend.model
2+
3+
import noweekend.core.domain.tag.Tag
4+
import noweekend.core.domain.tag.UserTags
5+
6+
data class TagRequest(
7+
val userTag: UserTagsForRequest,
8+
)
9+
10+
data class UserTagsForRequest(
11+
val selectedBasicTags: List<SimpleTag>,
12+
val unselectedBasicTags: List<SimpleTag>,
13+
val selectedCustomTags: List<SimpleTag>,
14+
val unselectedCustomTags: List<SimpleTag>,
15+
)
16+
17+
fun UserTags.toRequestType(): UserTagsForRequest {
18+
fun List<Tag>.toSimple() = map { SimpleTag(it.content) }
19+
return UserTagsForRequest(
20+
selectedBasicTags = selectedBasicTags.toSimple(),
21+
unselectedBasicTags = unselectedBasicTags.toSimple(),
22+
selectedCustomTags = selectedCustomTags.toSimple(),
23+
unselectedCustomTags = unselectedCustomTags.toSimple(),
24+
)
25+
}
26+
27+
data class SimpleTag(
28+
val content: String,
29+
)

noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/RecommendController.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package noweekend.core.api.controller.v1
22

33
import noweekend.client.mcp.recommend.model.SandwichResponse
4-
import noweekend.client.mcp.recommend.model.TagApiResponses
54
import noweekend.core.api.controller.v1.docs.RecommendControllerDocs
65
import noweekend.core.api.controller.v1.request.GenerateVacationRequest
76
import noweekend.core.api.controller.v1.response.AiGenerateVacationApiResponse
87
import noweekend.core.api.controller.v1.response.WeatherResponse
98
import noweekend.core.api.security.annotations.CurrentUserId
109
import noweekend.core.domain.IconStyle
1110
import noweekend.core.domain.recommend.RecommendService
11+
import noweekend.core.domain.tag.TagRecommendations
1212
import noweekend.core.support.response.ApiResponse
1313
import org.springframework.web.bind.annotation.GetMapping
1414
import org.springframework.web.bind.annotation.PostMapping
@@ -33,7 +33,7 @@ class RecommendController(
3333
@GetMapping("/todo/mixed")
3434
override fun getTagRecommendMixed(
3535
@CurrentUserId userId: String,
36-
): ApiResponse<TagApiResponses> {
36+
): ApiResponse<TagRecommendations> {
3737
return ApiResponse.success(
3838
recommendService.getTagRecommend(userId),
3939
)
@@ -42,7 +42,7 @@ class RecommendController(
4242
@GetMapping("/todo/new-only")
4343
override fun getTagRecommendOnlyNew(
4444
@CurrentUserId userId: String,
45-
): ApiResponse<TagApiResponses> {
45+
): ApiResponse<TagRecommendations> {
4646
return ApiResponse.success(
4747
recommendService.getTagRecommendOnlyNew(userId),
4848
)

noweekend-core/core-api/src/main/kotlin/noweekend/core/api/controller/v1/docs/RecommendControllerDocs.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import io.swagger.v3.oas.annotations.media.ExampleObject
77
import io.swagger.v3.oas.annotations.media.Schema
88
import io.swagger.v3.oas.annotations.tags.Tag
99
import noweekend.client.mcp.recommend.model.SandwichResponse
10-
import noweekend.client.mcp.recommend.model.TagApiResponses
1110
import noweekend.core.api.controller.v1.request.GenerateVacationRequest
1211
import noweekend.core.api.controller.v1.response.AiGenerateVacationApiResponse
1312
import noweekend.core.api.controller.v1.response.WeatherResponse
1413
import noweekend.core.api.security.annotations.CurrentUserId
14+
import noweekend.core.domain.tag.TagRecommendations
1515
import noweekend.core.support.response.ApiResponse
1616
import org.springframework.web.bind.annotation.RequestBody
1717
import io.swagger.v3.oas.annotations.responses.ApiResponse as SwaggerApiResponse
@@ -154,7 +154,7 @@ interface RecommendControllerDocs {
154154
)
155155
fun getTagRecommendMixed(
156156
@Parameter(hidden = true) @CurrentUserId userId: String,
157-
): ApiResponse<TagApiResponses>
157+
): ApiResponse<TagRecommendations>
158158

159159
@Operation(
160160
summary = "유저 태그 기반 완전히 새로운 추천 태그 3개 반환 - 마이페이지에서 할일 수정시 사용",
@@ -246,7 +246,7 @@ interface RecommendControllerDocs {
246246
)
247247
fun getTagRecommendOnlyNew(
248248
@Parameter(hidden = true) @CurrentUserId userId: String,
249-
): ApiResponse<TagApiResponses>
249+
): ApiResponse<TagRecommendations>
250250

251251
@Operation(
252252
summary = "샌드위치 연휴 추천",

noweekend-core/core-api/src/main/kotlin/noweekend/core/api/service/schedule/ScheduleApplicationService.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import noweekend.core.api.controller.v1.request.ScheduleCreateRequest
44
import noweekend.core.api.controller.v1.request.ScheduleUpdateRequest
55
import noweekend.core.api.controller.v1.response.DailyScheduleResponse
66
import noweekend.core.api.controller.v1.response.ScheduleResponse
7-
import noweekend.core.domain.tag.Schedule
8-
import noweekend.core.domain.tag.ScheduleReader
9-
import noweekend.core.domain.tag.ScheduleWriter
7+
import noweekend.core.domain.schedule.Schedule
8+
import noweekend.core.domain.schedule.ScheduleReader
9+
import noweekend.core.domain.schedule.ScheduleWriter
1010
import noweekend.core.support.error.CoreException
1111
import noweekend.core.support.error.ErrorType
1212
import org.springframework.stereotype.Service
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package noweekend.core.domain.recommend
22

33
import noweekend.client.mcp.recommend.model.SandwichResponse
4-
import noweekend.client.mcp.recommend.model.TagApiResponses
54
import noweekend.core.api.controller.v1.response.WeatherResponse
5+
import noweekend.core.domain.tag.TagRecommendations
66

77
interface RecommendService {
88
fun getWeatherRecommend(userId: String): WeatherResponse
9-
fun getTagRecommend(userId: String): TagApiResponses
10-
fun getTagRecommendOnlyNew(userId: String): TagApiResponses
9+
fun getTagRecommend(userId: String): TagRecommendations
10+
fun getTagRecommendOnlyNew(userId: String): TagRecommendations
1111
fun getSandwich(userId: String): SandwichResponse
1212
}

noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/recommend/RecommendServiceImpl.kt

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package noweekend.core.domain.recommend
22

3+
import com.fasterxml.jackson.databind.ObjectMapper
34
import noweekend.client.mcp.recommend.RecommendClient
45
import noweekend.client.mcp.recommend.model.SandwichRequest
56
import noweekend.client.mcp.recommend.model.SandwichResponse
6-
import noweekend.client.mcp.recommend.model.TagApiResponses
7-
import noweekend.client.mcp.recommend.model.TagResponse
87
import noweekend.client.mcp.recommend.model.WeatherRequest
98
import noweekend.core.api.controller.v1.response.WeatherResponse
109
import noweekend.core.domain.holiday.HolidayReader
10+
import noweekend.core.domain.tag.RecommendType
1111
import noweekend.core.domain.tag.TagReader
12+
import noweekend.core.domain.tag.TagRecommendCache
13+
import noweekend.core.domain.tag.TagRecommendCacheReader
14+
import noweekend.core.domain.tag.TagRecommendCacheWriter
15+
import noweekend.core.domain.tag.TagRecommendation
16+
import noweekend.core.domain.tag.TagRecommendations
1217
import noweekend.core.domain.tag.UserTags
1318
import noweekend.core.domain.user.Location
1419
import noweekend.core.domain.user.UserReader
@@ -30,6 +35,9 @@ class RecommendServiceImpl(
3035
private val holidayReader: HolidayReader,
3136
private val weatherReader: WeatherReader,
3237
private val weatherWriter: WeatherWriter,
38+
private val tagRecommendCacheReader: TagRecommendCacheReader,
39+
private val tagRecommendCacheWriter: TagRecommendCacheWriter,
40+
private val objectMapper: ObjectMapper,
3341
) : RecommendService {
3442

3543
override fun getWeatherRecommend(userId: String): WeatherResponse {
@@ -43,7 +51,7 @@ class RecommendServiceImpl(
4351
val apiResponse = fetchWeatherFromApi(location)
4452
saveWeatherCache(location, today, apiResponse)
4553
return WeatherResponse(apiResponse)
46-
} catch (e: Exception) {
54+
} catch (_: Exception) {
4755
throw CoreException(ErrorType.MCP_SERVER_WEATHER_ERROR)
4856
}
4957
}
@@ -70,7 +78,7 @@ class RecommendServiceImpl(
7078
val request = WeatherRequest(location.longitude, location.latitude)
7179
try {
7280
return recommendClient.getFutureWeather(request)
73-
} catch (e: Exception) {
81+
} catch (_: Exception) {
7482
throw CoreException(ErrorType.MCP_SERVER_WEATHER_ERROR)
7583
}
7684
}
@@ -93,13 +101,22 @@ class RecommendServiceImpl(
93101
weatherWriter.register(cacheObj)
94102
}
95103

96-
override fun getTagRecommend(userId: String): TagApiResponses {
104+
override fun getTagRecommend(userId: String): TagRecommendations {
97105
val userTags = tagReader.getUserTags(userId)
98106
userTagValidation(userTags)
99-
// ToDo 오늘 태그를 추천을 받았다면 받았던 걸로 반환하는 로직 추가
107+
val cached = getTagRecommendCaching(RecommendType.MIXED, userTags)
108+
109+
if (cached != null) {
110+
return cached.recommend
111+
}
100112

101113
val apiRecommendResponse = recommendClient.getRecommend(userTags)
102114
if (apiRecommendResponse != null) {
115+
registerTagRecommendCache(
116+
type = RecommendType.MIXED,
117+
userTags = userTags,
118+
apiResponse = apiRecommendResponse,
119+
)
103120
return apiRecommendResponse
104121
}
105122

@@ -113,21 +130,49 @@ class RecommendServiceImpl(
113130
}
114131
}
115132

116-
private fun mcpClientNotResponding(userTags: UserTags): TagApiResponses {
133+
private fun getTagRecommendCaching(tagRecommendType: RecommendType, userTags: UserTags): TagRecommendCache? {
134+
val tagsJson = objectMapper.writeValueAsString(userTags)
135+
return tagRecommendCacheReader.findTodayCache(
136+
recommendType = tagRecommendType,
137+
tagsJson = tagsJson,
138+
searchDate = LocalDate.now(),
139+
)
140+
}
141+
142+
private fun registerTagRecommendCache(
143+
type: RecommendType,
144+
userTags: UserTags,
145+
apiResponse: TagRecommendations,
146+
) {
147+
val cache = TagRecommendCache.register(
148+
recommendType = type,
149+
searchDate = LocalDate.now(),
150+
tags = userTags,
151+
recommend = apiResponse,
152+
)
153+
tagRecommendCacheWriter.register(cache)
154+
}
155+
156+
private fun mcpClientNotResponding(userTags: UserTags): TagRecommendations {
117157
val selectedTags = userTags.selectedBasicTags + userTags.selectedCustomTags
118158
val shuffled = selectedTags.shuffled(Random(System.currentTimeMillis()))
119159

120-
return TagApiResponses(
121-
firstRecommendTag = TagResponse(shuffled[0].content),
122-
secondRecommendTag = TagResponse(shuffled[1].content),
123-
thirdRecommendTag = TagResponse(shuffled[2].content),
160+
return TagRecommendations(
161+
firstRecommendTag = TagRecommendation(shuffled[0].content),
162+
secondRecommendTag = TagRecommendation(shuffled[1].content),
163+
thirdRecommendTag = TagRecommendation(shuffled[2].content),
124164
)
125165
}
126166

127-
override fun getTagRecommendOnlyNew(userId: String): TagApiResponses {
167+
override fun getTagRecommendOnlyNew(userId: String): TagRecommendations {
128168
val userTags = tagReader.getUserTags(userId)
129169
userTagValidation(userTags)
130170

171+
val cached = getTagRecommendCaching(RecommendType.ONLY_NEW, userTags)
172+
if (cached != null) {
173+
return cached.recommend
174+
}
175+
131176
val apiRecommendResponse = recommendClient.getOnlyNewRecommend(userTags)
132177
if (apiRecommendResponse != null) {
133178
val allOldTags = (
@@ -143,6 +188,7 @@ class RecommendServiceImpl(
143188
apiRecommendResponse.thirdRecommendTag.content,
144189
)
145190
require(allNewTags.none { it in allOldTags }) { "Returned tag already exists in user tags" }
191+
registerTagRecommendCache(RecommendType.ONLY_NEW, userTags, apiRecommendResponse)
146192
return apiRecommendResponse
147193
}
148194

noweekend-core/core-api/src/main/kotlin/noweekend/core/domain/user/UserServiceImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import noweekend.core.api.controller.v1.request.ProfileRequest
66
import noweekend.core.api.controller.v1.request.TagUpdateRequest
77
import noweekend.core.api.controller.v1.response.UserInformationResponse
88
import noweekend.core.domain.enumerate.ScheduleCategory
9+
import noweekend.core.domain.schedule.ScheduleReader
910
import noweekend.core.domain.tag.BasicTag
10-
import noweekend.core.domain.tag.ScheduleReader
1111
import noweekend.core.domain.tag.TagReader
1212
import noweekend.core.domain.tag.TagWriter
1313
import noweekend.core.domain.tag.UserTags

0 commit comments

Comments
 (0)