Skip to content

Commit 914276e

Browse files
authored
refactor(be) : Tour Service 리팩토링 (#118)
* refactor(be) : TourService 리팩토링 * refactor(be) : 단일책임(SRP), 기능별 인터페이스(ISP) 리팩토링 * refactor(be) : Tour 캐시 개선
1 parent fbb2213 commit 914276e

File tree

10 files changed

+254
-153
lines changed

10 files changed

+254
-153
lines changed
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
11
package com.back.koreaTravelGuide.domain.ai.tour.cache
22

3-
// TODO: 관광 정보 캐시 설정 - 캐시 주기 및 정책 관리 (필요시 구현)
4-
class TourCacheConfig
3+
import com.back.koreaTravelGuide.common.logging.log
4+
import org.springframework.cache.annotation.CacheEvict
5+
import org.springframework.context.annotation.Configuration
6+
import org.springframework.scheduling.annotation.Scheduled
7+
8+
@Configuration
9+
class TourCacheConfig {
10+
companion object {
11+
private val TOUR_CACHE_NAMES = arrayOf("tourAreaBased", "tourLocationBased", "tourDetail")
12+
}
13+
14+
// 매일 자정(00:00)마다 모든 캐시 항목을 무효화
15+
@CacheEvict(cacheNames = ["tourAreaBased", "tourLocationBased", "tourDetail"], allEntries = true)
16+
@Scheduled(cron = "0 0 0 * * ?", zone = "Asia/Seoul")
17+
fun clearTourCaches() {
18+
log.info("clearTourCaches - evicting {}", TOUR_CACHE_NAMES.joinToString())
19+
}
20+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.back.koreaTravelGuide.domain.ai.tour.service
2+
3+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams
4+
import org.springframework.stereotype.Component
5+
6+
@Component
7+
class TourParamsParser {
8+
fun parse(
9+
contentTypeId: String,
10+
areaAndSigunguCode: String,
11+
): TourParams {
12+
val codes = areaAndSigunguCode.split(",").map { it.trim() }
13+
14+
val areaCode = codes.getOrNull(0)
15+
val sigunguCode = codes.getOrNull(1)
16+
17+
return TourParams(
18+
contentTypeId = contentTypeId,
19+
areaCode = areaCode,
20+
sigunguCode = sigunguCode,
21+
)
22+
}
23+
}
Lines changed: 11 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,42 @@
11
package com.back.koreaTravelGuide.domain.ai.tour.service
22

3-
import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient
4-
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailItem
53
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams
64
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse
7-
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourItem
85
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourLocationBasedParams
96
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams
107
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse
11-
import org.slf4j.LoggerFactory
12-
import org.springframework.cache.annotation.Cacheable
8+
import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourAreaBasedUseCase
9+
import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourDetailUseCase
10+
import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourLocationBasedUseCase
1311
import org.springframework.stereotype.Service
1412

1513
// 09.26 양현준
1614
@Service
1715
class TourService(
18-
private val tourApiClient: TourApiClient,
16+
private val tourAreaBasedUseCase: TourAreaBasedUseCase,
17+
private val tourLocationBasedUseCase: TourLocationBasedUseCase,
18+
private val tourDetailUseCase: TourDetailUseCase,
19+
private val tourParamsParser: TourParamsParser,
1920
) {
20-
private val logger = LoggerFactory.getLogger(this::class.java)
21-
22-
// 파라미터를 TourParams DTO에 맞게 파싱
2321
fun parseParams(
2422
contentTypeId: String,
2523
areaAndSigunguCode: String,
2624
): TourParams {
27-
val codes = areaAndSigunguCode.split(",").map { it.trim() }
28-
29-
val areaCode = codes.getOrNull(0)
30-
val sigunguCode = codes.getOrNull(1)
31-
32-
return TourParams(
33-
contentTypeId = contentTypeId,
34-
areaCode = areaCode,
35-
sigunguCode = sigunguCode,
36-
)
25+
return tourParamsParser.parse(contentTypeId, areaAndSigunguCode)
3726
}
3827

39-
// API 호출 1, 지역기반 관광정보 조회 - areaBasedList2
40-
@Cacheable(
41-
"tourAreaBased",
42-
key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode",
43-
)
4428
fun fetchTours(tourParams: TourParams): TourResponse {
45-
// 09.30 테스트용 하드코딩
46-
if (
47-
tourParams.contentTypeId == "12" &&
48-
tourParams.areaCode == "6" &&
49-
tourParams.sigunguCode == "10"
50-
) {
51-
return PRESET_AREA_TOUR_RESPONSE
52-
}
53-
54-
val tours = tourApiClient.fetchTourInfo(tourParams)
55-
56-
return tours
29+
return tourAreaBasedUseCase.fetchAreaBasedTours(tourParams)
5730
}
5831

59-
// API 호출 2, 위치기반 관광정보 조회 - locationBasedList2
60-
@Cacheable(
61-
"tourLocationBased",
62-
key =
63-
"#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode + " +
64-
"'_' + #locationParams.mapX + '_' + #locationParams.mapY + '_' + #locationParams.radius",
65-
)
6632
fun fetchLocationBasedTours(
6733
tourParams: TourParams,
6834
locationParams: TourLocationBasedParams,
6935
): TourResponse {
70-
// 09.30 테스트용 하드코딩
71-
if (
72-
tourParams.contentTypeId == "39" &&
73-
tourParams.areaCode == "1" &&
74-
tourParams.sigunguCode == "24" &&
75-
locationParams.mapX == "126.98375" &&
76-
locationParams.mapY == "37.563446" &&
77-
locationParams.radius == "100"
78-
) {
79-
return PRESET_LOCATION_BASED_RESPONSE
80-
}
81-
82-
return tourApiClient.fetchLocationBasedTours(tourParams, locationParams)
36+
return tourLocationBasedUseCase.fetchLocationBasedTours(tourParams, locationParams)
8337
}
8438

85-
// APi 호출 3, 관광정보 상세조회 - detailCommon2
86-
@Cacheable("tourDetail", key = "#detailParams.contentId")
8739
fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse {
88-
// 09.30 테스트용 하드코딩
89-
if (
90-
detailParams.contentId == "127974"
91-
) {
92-
return PRESET_DETAIL_RESPONSE
93-
}
94-
95-
return tourApiClient.fetchTourDetail(detailParams)
40+
return tourDetailUseCase.fetchTourDetail(detailParams)
9641
}
9742
}
98-
99-
/**
100-
* 09.30 테스트용 하드코딩
101-
* "areacode": "6" 부산
102-
* "sigungucode": "10" 사하구
103-
* "contenttypeid": "12" 관광지
104-
* 실제 API 호출 대신, 정해진 응답을 반환
105-
*/
106-
private val PRESET_AREA_TOUR_RESPONSE =
107-
TourResponse(
108-
items =
109-
listOf(
110-
TourItem(
111-
contentId = "127974",
112-
contentTypeId = "12",
113-
createdTime = "20031208090000",
114-
modifiedTime = "20250411180037",
115-
title = "을숙도 공원",
116-
addr1 = "부산광역시 사하구 낙동남로 1240 (하단동)",
117-
areaCode = "6",
118-
firstimage = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image2_1.jpg",
119-
firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image3_1.jpg",
120-
mapX = "128.9460030322",
121-
mapY = "35.1045320626",
122-
distance = null,
123-
mlevel = "6",
124-
sigunguCode = "10",
125-
lDongRegnCd = "26",
126-
lDongSignguCd = "380",
127-
),
128-
),
129-
)
130-
131-
private val PRESET_LOCATION_BASED_RESPONSE =
132-
TourResponse(
133-
items =
134-
listOf(
135-
TourItem(
136-
contentId = "133858",
137-
contentTypeId = "39",
138-
createdTime = "20030529090000",
139-
modifiedTime = "20250409105941",
140-
title = "백제삼계탕",
141-
addr1 = "서울특별시 중구 명동8길 8-10 (명동2가)",
142-
areaCode = "1",
143-
firstimage = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image2_1.JPG",
144-
firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/85/3108585_image3_1.JPG",
145-
mapX = "126.9841178194",
146-
mapY = "37.5634241535",
147-
distance = "32.788938679922325",
148-
mlevel = "6",
149-
sigunguCode = "24",
150-
lDongRegnCd = "11",
151-
lDongSignguCd = "140",
152-
),
153-
),
154-
)
155-
156-
private val PRESET_DETAIL_RESPONSE =
157-
TourDetailResponse(
158-
items =
159-
listOf(
160-
TourDetailItem(
161-
contentId = "126128",
162-
title = "동촌유원지",
163-
overview =
164-
"동촌유원지는 대구시 동쪽 금호강변에 있는 44만 평의 유원지로 오래전부터 대구 시민이 즐겨 찾는 곳이다. " +
165-
"각종 위락시설이 잘 갖춰져 있으며, 드라이브를 즐길 수 있는 도로가 건설되어 있다. 수량이 많은 금호강에는 조교가 가설되어 있고, " +
166-
"우아한 다리 이름을 가진 아양교가 걸쳐 있다. 금호강(琴湖江)을 끼고 있어 예로부터 봄에는 그네뛰기, 봉숭아꽃 구경, " +
167-
"여름에는 수영과 보트 놀이, 가을에는 밤 줍기 등 즐길 거리가 많은 곳이다. 또한, 해맞이다리, 유선장, 체육시설, " +
168-
"실내 롤러스케이트장 등 다양한 즐길 거리가 있어 여행의 재미를 더해준다.",
169-
addr1 = "대구광역시 동구 효목동",
170-
mapX = "128.6506352387",
171-
mapY = "35.8826195757",
172-
firstImage = "http://tongit g.visitkorea.or.kr/cms/resource/86/3488286_image2_1.JPG",
173-
tel = "",
174-
homepage =
175-
"",
176-
),
177-
),
178-
)

src/main/kotlin/com/back/koreaTravelGuide/domain/ai/tour/service/TourServiceCore.kt

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.back.koreaTravelGuide.domain.ai.tour.service.core
2+
3+
import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient
4+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourItem
5+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams
6+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourResponse
7+
import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourAreaBasedUseCase
8+
import org.springframework.cache.annotation.Cacheable
9+
import org.springframework.stereotype.Service
10+
11+
@Service
12+
class TourAreaBasedServiceCore(
13+
private val tourApiClient: TourApiClient,
14+
) : TourAreaBasedUseCase {
15+
@Cacheable(
16+
"tourAreaBased",
17+
key = "#tourParams.contentTypeId + '_' + #tourParams.areaCode + '_' + #tourParams.sigunguCode",
18+
unless = "#result == null",
19+
)
20+
override fun fetchAreaBasedTours(tourParams: TourParams): TourResponse {
21+
if (
22+
tourParams.contentTypeId == "12" &&
23+
tourParams.areaCode == "6" &&
24+
tourParams.sigunguCode == "10"
25+
) {
26+
return PRESET_AREA_TOUR_RESPONSE
27+
}
28+
29+
return tourApiClient.fetchTourInfo(tourParams)
30+
}
31+
32+
private companion object {
33+
val PRESET_AREA_TOUR_RESPONSE =
34+
TourResponse(
35+
items =
36+
listOf(
37+
TourItem(
38+
contentId = "127974",
39+
contentTypeId = "12",
40+
createdTime = "20031208090000",
41+
modifiedTime = "20250411180037",
42+
title = "을숙도 공원",
43+
addr1 = "부산광역시 사하구 낙동남로 1240 (하단동)",
44+
areaCode = "6",
45+
firstimage = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image2_1.jpg",
46+
firstimage2 = "http://tong.visitkorea.or.kr/cms/resource/62/2487962_image3_1.jpg",
47+
mapX = "128.9460030322",
48+
mapY = "35.1045320626",
49+
distance = null,
50+
mlevel = "6",
51+
sigunguCode = "10",
52+
lDongRegnCd = "26",
53+
lDongSignguCd = "380",
54+
),
55+
),
56+
)
57+
}
58+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.back.koreaTravelGuide.domain.ai.tour.service.core
2+
3+
import com.back.koreaTravelGuide.domain.ai.tour.client.TourApiClient
4+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailItem
5+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailParams
6+
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourDetailResponse
7+
import com.back.koreaTravelGuide.domain.ai.tour.service.usecase.TourDetailUseCase
8+
import org.springframework.cache.annotation.Cacheable
9+
import org.springframework.stereotype.Service
10+
11+
@Service
12+
class TourDetailServiceCore(
13+
private val tourApiClient: TourApiClient,
14+
) : TourDetailUseCase {
15+
@Cacheable("tourDetail", key = "#detailParams.contentId", unless = "#result == null")
16+
override fun fetchTourDetail(detailParams: TourDetailParams): TourDetailResponse {
17+
if (detailParams.contentId == "127974") {
18+
return PRESET_DETAIL_RESPONSE
19+
}
20+
21+
return tourApiClient.fetchTourDetail(detailParams)
22+
}
23+
24+
private companion object {
25+
val PRESET_DETAIL_RESPONSE =
26+
TourDetailResponse(
27+
items =
28+
listOf(
29+
TourDetailItem(
30+
contentId = "126128",
31+
title = "동촌유원지",
32+
overview =
33+
"동촌유원지는 대구시 동쪽 금호강변에 있는 44만 평의 유원지로 오래전부터 대구 시민이 즐겨 찾는 곳이다. " +
34+
"각종 위락시설이 잘 갖춰져 있으며, 드라이브를 즐길 수 있는 도로가 건설되어 있다. 수량이 많은 금호강에는 조교가 가설되어 있고, " +
35+
"우아한 다리 이름을 가진 아양교가 걸쳐 있다. 금호강(琴湖江)을 끼고 있어 예로부터 봄에는 그네뛰기, 봉숭아꽃 구경, " +
36+
"여름에는 수영과 보트 놀이, 가을에는 밤 줍기 등 즐길 거리가 많은 곳이다. 또한, 해맞이다리, 유선장, 체육시설, " +
37+
"실내 롤러스케이트장 등 다양한 즐길 거리가 있어 여행의 재미를 더해준다.",
38+
addr1 = "대구광역시 동구 효목동",
39+
mapX = "128.6506352387",
40+
mapY = "35.8826195757",
41+
firstImage = "http://tongit g.visitkorea.or.kr/cms/resource/86/3488286_image2_1.JPG",
42+
tel = "",
43+
homepage =
44+
"",
45+
),
46+
),
47+
)
48+
}
49+
}

0 commit comments

Comments
 (0)