diff --git a/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CoursePlace.kt b/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CoursePlace.kt index 44ffc4bd..496898bc 100644 --- a/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CoursePlace.kt +++ b/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CoursePlace.kt @@ -8,32 +8,17 @@ import com.piikii.application.domain.schedule.ScheduleType data class CoursePlace( val scheduleId: LongTypeId, val scheduleType: ScheduleType, - val placeId: LongTypeId, - val name: String, - val url: String?, - val address: String?, - val phoneNumber: String?, - val coordinate: Coordinate?, - val distance: Distance?, + val prePlace: Place?, + val place: Place, + val coordinate: Coordinate, + val distance: Distance, ) { - companion object { - fun from( - schedule: Schedule, - place: Place, - coordinate: Coordinate?, - distance: Distance?, - ): CoursePlace { - return CoursePlace( - scheduleId = schedule.id, - scheduleType = schedule.type, - placeId = place.id, - name = place.name, - url = place.url, - address = place.address, - phoneNumber = place.phoneNumber, - coordinate = coordinate, - distance = distance, - ) - } - } + constructor(schedule: Schedule, prePlace: Place?, place: Place, distance: Distance) : this( + scheduleId = schedule.id, + scheduleType = schedule.type, + prePlace = prePlace, + place = place, + coordinate = place.getCoordinate(), + distance = distance, + ) } diff --git a/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CourseService.kt b/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CourseService.kt index c4c3ccb4..eb9c6663 100644 --- a/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CourseService.kt +++ b/piikii-application/src/main/kotlin/com/piikii/application/domain/course/CourseService.kt @@ -82,24 +82,15 @@ class CourseService( places: List, agreeCountByPlaceId: Map, ): Map { - // initial 값 설정: null과 빈 Map의 쌍으로 초기화 - val initial: Map = emptyMap() - + val initCourse: Map = emptyMap() return mapPlacesBySchedule(schedules, places) - .entries.fold(initial) { prePlaceBySchedule, (schedule, places) -> - // 현재 CoursePlace 생성 + .entries.fold(initCourse) { course, (schedule, places) -> val confirmedPlace = getConfirmedPlace(schedule, places, agreeCountByPlaceId) - - if (confirmedPlace != null) { - val preCoursePlace = prePlaceBySchedule.values.lastOrNull() - val curCoursePlace = getCoursePlace(schedule, preCoursePlace, confirmedPlace) - - // currentCoursePlace를 누적된 placeBySchedule 맵에 추가 - prePlaceBySchedule + (schedule to curCoursePlace) - } else { - // confirmedPlace가 null인 경우 기존 맵을 그대로 반환 - prePlaceBySchedule - } + confirmedPlace?.let { + val preCoursePlace = course.values.lastOrNull() + val curCoursePlace = getCoursePlace(schedule, preCoursePlace?.place, it) + course + (schedule to curCoursePlace) + } ?: course } } @@ -119,21 +110,17 @@ class CourseService( private fun getCoursePlace( schedule: Schedule, - preCoursePlace: CoursePlace?, + prePlace: Place?, confirmedPlace: Place, ): CoursePlace { - val coordinate = confirmedPlace.getCoordinate() - - return CoursePlace.from( + return CoursePlace( schedule = schedule, + prePlace = prePlace, place = confirmedPlace, - coordinate = coordinate, distance = - preCoursePlace?.coordinate?.let { preCoordinate -> - coordinate.let { coordinate -> - navigationPort.getDistance(start = preCoordinate, end = coordinate) - } - }, + prePlace?.let { + navigationPort.getDistance(it, confirmedPlace) + } ?: Distance.EMPTY, ) } diff --git a/piikii-application/src/main/kotlin/com/piikii/application/domain/course/Distance.kt b/piikii-application/src/main/kotlin/com/piikii/application/domain/course/Distance.kt index 9b7850c2..8283a884 100644 --- a/piikii-application/src/main/kotlin/com/piikii/application/domain/course/Distance.kt +++ b/piikii-application/src/main/kotlin/com/piikii/application/domain/course/Distance.kt @@ -1,10 +1,11 @@ package com.piikii.application.domain.course data class Distance( - val totalDistanceMeter: Int?, - val totalTimeMinute: Int?, + val totalDistanceMeter: Int? = null, + val totalTimeMinute: Int? = null, ) { companion object { + @JvmField val EMPTY = Distance(null, null) } } diff --git a/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/response/CourseResponse.kt b/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/response/CourseResponse.kt index 9144a963..067f3783 100644 --- a/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/response/CourseResponse.kt +++ b/piikii-application/src/main/kotlin/com/piikii/application/port/input/dto/response/CourseResponse.kt @@ -51,13 +51,13 @@ data class CoursePlaceResponse( return CoursePlaceResponse( scheduleId = coursePlace.scheduleId.getValue(), scheduleType = coursePlace.scheduleType, - placeId = coursePlace.placeId.getValue(), - name = coursePlace.name, - url = coursePlace.url, - address = coursePlace.address, - phoneNumber = coursePlace.phoneNumber, - distance = coursePlace.distance?.totalDistanceMeter, - time = coursePlace.distance?.totalTimeMinute, + placeId = coursePlace.place.id.getValue(), + name = coursePlace.place.name, + url = coursePlace.place.url, + address = coursePlace.place.address, + phoneNumber = coursePlace.place.phoneNumber, + distance = coursePlace.distance.totalDistanceMeter, + time = coursePlace.distance.totalTimeMinute, ) } } diff --git a/piikii-application/src/main/kotlin/com/piikii/application/port/output/web/NavigationPort.kt b/piikii-application/src/main/kotlin/com/piikii/application/port/output/web/NavigationPort.kt index 951815ab..76680d90 100644 --- a/piikii-application/src/main/kotlin/com/piikii/application/port/output/web/NavigationPort.kt +++ b/piikii-application/src/main/kotlin/com/piikii/application/port/output/web/NavigationPort.kt @@ -1,11 +1,11 @@ package com.piikii.application.port.output.web -import com.piikii.application.domain.course.Coordinate import com.piikii.application.domain.course.Distance +import com.piikii.application.domain.place.Place interface NavigationPort { fun getDistance( - start: Coordinate, - end: Coordinate, + startPlace: Place, + endPlace: Place, ): Distance } diff --git a/piikii-application/src/test/kotlin/com/piikii/application/domain/course/CourseServiceTest.kt b/piikii-application/src/test/kotlin/com/piikii/application/domain/course/CourseServiceTest.kt index c81306f9..3fb2487b 100644 --- a/piikii-application/src/test/kotlin/com/piikii/application/domain/course/CourseServiceTest.kt +++ b/piikii-application/src/test/kotlin/com/piikii/application/domain/course/CourseServiceTest.kt @@ -132,9 +132,7 @@ class CourseServiceTest { ), ) - val coordinate1 = Coordinate(places[0].longitude, places[0].latitude) - val coordinate2 = Coordinate(places[2].longitude, places[2].latitude) - given(navigationPort.getDistance(coordinate1, coordinate2)) + given(navigationPort.getDistance(places[0], places[2])) .willReturn(Distance(100, 5)) val updatedPlace = places[2].copy(confirmed = true) diff --git a/piikii-bootstrap/src/main/resources/application.yml b/piikii-bootstrap/src/main/resources/application.yml index c1fcc98d..3d03faa3 100644 --- a/piikii-bootstrap/src/main/resources/application.yml +++ b/piikii-bootstrap/src/main/resources/application.yml @@ -7,6 +7,7 @@ spring: - classpath:avocado-config/application.yml - classpath:tmap-config/application.yml - classpath:application-actuator.yml + - classpath:cache-config/application.yml application: name: "piikii" messages: diff --git a/piikii-bootstrap/src/test/resources/application.yml b/piikii-bootstrap/src/test/resources/application.yml index d2143e51..a1d53417 100644 --- a/piikii-bootstrap/src/test/resources/application.yml +++ b/piikii-bootstrap/src/test/resources/application.yml @@ -7,6 +7,7 @@ spring: - classpath:avocado-config/application.yml - classpath:database-config/application-test.yml - classpath:tmap-config/application.yml + - classpath:cache-config/application.yml application: name: "piikii" messages: diff --git a/piikii-output-cache/redis/src/main/kotlin/com/piikii/output/redis/config/RedisConfig.kt b/piikii-output-cache/redis/src/main/kotlin/com/piikii/output/redis/config/RedisConfig.kt index db1dcaa0..4e21f4e3 100644 --- a/piikii-output-cache/redis/src/main/kotlin/com/piikii/output/redis/config/RedisConfig.kt +++ b/piikii-output-cache/redis/src/main/kotlin/com/piikii/output/redis/config/RedisConfig.kt @@ -1,48 +1,82 @@ package com.piikii.output.redis.config -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.MapperFeature import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.SerializationFeature -import com.fasterxml.jackson.databind.json.JsonMapper -import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.cache.CacheManager +import org.springframework.cache.annotation.EnableCaching import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.data.redis.cache.RedisCacheConfiguration +import org.springframework.data.redis.cache.RedisCacheManager +import org.springframework.data.redis.connection.RedisConnectionFactory +import org.springframework.data.redis.connection.RedisPassword +import org.springframework.data.redis.connection.RedisStandaloneConfiguration +import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory import org.springframework.data.redis.core.RedisTemplate +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer +import org.springframework.data.redis.serializer.RedisSerializationContext import org.springframework.data.redis.serializer.StringRedisSerializer +import java.time.Duration -// TODO: 필요 시에 등록을 위해 설정 -// @Configuration +@Configuration +@EnableRedisRepositories +@EnableCaching +@EnableConfigurationProperties(RedisProperties::class) class RedisConfig { - @Value("\${redis.host}") - private val redisHost: String? = null + @Bean + fun lettuceConnectionFactory(redisProperties: RedisProperties): LettuceConnectionFactory { + val redisConfig = + RedisStandaloneConfiguration().apply { + hostName = redisProperties.host + port = redisProperties.port + password = RedisPassword.of(redisProperties.password) + } - @Value("\${redis.port}") - private val redisPort = 0 + val clientConfig = + LettuceClientConfiguration.builder() + .useSsl() + .build() - @Bean - fun objectMapper(): ObjectMapper { - return JsonMapper.builder() - .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true) - .configure(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE, false) - .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(DeserializationFeature.READ_ENUMS_USING_TO_STRING, true) - .findAndAddModules() - .build() + return LettuceConnectionFactory(redisConfig, clientConfig) } @Bean - fun lettuceConnectionFactory(): LettuceConnectionFactory { - return LettuceConnectionFactory(redisHost!!, redisPort) + fun redisTemplate( + redisConnectionFactory: RedisConnectionFactory, + objectMapper: ObjectMapper, + ): RedisTemplate { + val redisTemplate = RedisTemplate() + redisTemplate.connectionFactory = redisConnectionFactory + redisTemplate.keySerializer = StringRedisSerializer() + redisTemplate.valueSerializer = GenericJackson2JsonRedisSerializer(objectMapper) + return redisTemplate } @Bean - fun redistemplate(): RedisTemplate { - val redisTemplate = RedisTemplate() - redisTemplate.connectionFactory = lettuceConnectionFactory() - redisTemplate.keySerializer = StringRedisSerializer() - redisTemplate.valueSerializer = GenericJackson2JsonRedisSerializer(objectMapper()) - return redisTemplate + fun cacheManager(redisConnectionFactory: RedisConnectionFactory): CacheManager { + val redisCacheConfiguration = + RedisCacheConfiguration.defaultCacheConfig() + .entryTtl(Duration.ofDays(7)) + .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())) + .serializeValuesWith( + RedisSerializationContext.SerializationPair.fromSerializer( + GenericJackson2JsonRedisSerializer(), + ), + ) + + return RedisCacheManager.RedisCacheManagerBuilder + .fromConnectionFactory(redisConnectionFactory) + .cacheDefaults(redisCacheConfiguration) + .build() } } + +@ConfigurationProperties(prefix = "redis") +data class RedisProperties( + val host: String, + val port: Int, + val password: String, +) diff --git a/piikii-output-cache/redis/src/main/resources/cache-config/application-local.yml b/piikii-output-cache/redis/src/main/resources/cache-config/application-local.yml deleted file mode 100644 index 55fec8b9..00000000 --- a/piikii-output-cache/redis/src/main/resources/cache-config/application-local.yml +++ /dev/null @@ -1,7 +0,0 @@ -spring: - config: - activate: - on-profile: local -redis: - host: localhost - port: 6379 \ No newline at end of file diff --git a/piikii-output-cache/redis/src/main/resources/cache-config/application-prod.yml b/piikii-output-cache/redis/src/main/resources/cache-config/application-prod.yml deleted file mode 100644 index d07c1c1b..00000000 --- a/piikii-output-cache/redis/src/main/resources/cache-config/application-prod.yml +++ /dev/null @@ -1,7 +0,0 @@ -spring: - config: - activate: - on-profile: prod -redis: - host: localhost - port: 6379 \ No newline at end of file diff --git a/piikii-output-cache/redis/src/main/resources/cache-config/application.yml b/piikii-output-cache/redis/src/main/resources/cache-config/application.yml new file mode 100644 index 00000000..c9637c51 --- /dev/null +++ b/piikii-output-cache/redis/src/main/resources/cache-config/application.yml @@ -0,0 +1,5 @@ +redis: + host: ${REDIS_HOST} + port: 6379 + password: ${REDIS_PASSWORD} + ssl: true diff --git a/piikii-output-web/tmap/src/main/kotlin/com/piikii/output/web/tmap/adapter/TmapNavigationAdapter.kt b/piikii-output-web/tmap/src/main/kotlin/com/piikii/output/web/tmap/adapter/TmapNavigationAdapter.kt index 3772ba1b..01ec2324 100644 --- a/piikii-output-web/tmap/src/main/kotlin/com/piikii/output/web/tmap/adapter/TmapNavigationAdapter.kt +++ b/piikii-output-web/tmap/src/main/kotlin/com/piikii/output/web/tmap/adapter/TmapNavigationAdapter.kt @@ -2,9 +2,11 @@ package com.piikii.output.web.tmap.adapter import com.piikii.application.domain.course.Coordinate import com.piikii.application.domain.course.Distance +import com.piikii.application.domain.place.Place import com.piikii.application.port.output.web.NavigationPort import com.piikii.common.exception.ExceptionCode import com.piikii.common.exception.PiikiiException +import org.springframework.cache.annotation.Cacheable import org.springframework.stereotype.Component import org.springframework.web.client.RestClient import org.springframework.web.client.body @@ -13,12 +15,19 @@ import org.springframework.web.client.body class TmapNavigationAdapter( private val tmapApiClient: RestClient, ) : NavigationPort { + @Cacheable( + value = ["Distance"], + key = "#startPlace.id + '_' + #endPlace.id", + unless = "#result == T(com.piikii.application.domain.course.Distance).EMPTY", + ) override fun getDistance( - start: Coordinate, - end: Coordinate, + startPlace: Place, + endPlace: Place, ): Distance { - return if (start.isValid() && end.isValid()) { - getDistanceFromTmap(start, end) + val startCoordinate = startPlace.getCoordinate() + val endCoordinate = endPlace.getCoordinate() + return if (startCoordinate.isValid() && endCoordinate.isValid()) { + getDistanceFromTmap(startCoordinate, endCoordinate) } else { Distance.EMPTY } diff --git a/piikii-output-web/tmap/src/test/kotlin/com/piikii/output/web/tmap/TmapNavigationAdapterTest.kt b/piikii-output-web/tmap/src/test/kotlin/com/piikii/output/web/tmap/TmapNavigationAdapterTest.kt index e2f8bb71..930f475b 100644 --- a/piikii-output-web/tmap/src/test/kotlin/com/piikii/output/web/tmap/TmapNavigationAdapterTest.kt +++ b/piikii-output-web/tmap/src/test/kotlin/com/piikii/output/web/tmap/TmapNavigationAdapterTest.kt @@ -1,6 +1,10 @@ package com.piikii.output.web.tmap -import com.piikii.application.domain.course.Coordinate +import com.piikii.application.domain.generic.LongTypeId +import com.piikii.application.domain.generic.ThumbnailLinks +import com.piikii.application.domain.generic.UuidTypeId +import com.piikii.application.domain.place.Origin +import com.piikii.application.domain.place.Place import com.piikii.output.web.tmap.adapter.TmapNavigationAdapter import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test @@ -8,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ActiveProfiles import org.springframework.test.context.ContextConfiguration +import java.util.UUID @Disabled @SpringBootTest @@ -19,10 +24,46 @@ class TmapNavigationAdapterTest { @Test fun getDistanceTest() { - val start = Coordinate(x = 126.9246033, y = 33.45241976) - val end = Coordinate(x = 126.9041895, y = 33.4048969) + val startPlace = + Place( + id = LongTypeId(1L), + roomUid = UuidTypeId(UUID.randomUUID()), + scheduleId = LongTypeId(0L), + name = "", + url = null, + thumbnailLinks = ThumbnailLinks(listOf()), + address = null, + phoneNumber = null, + starGrade = null, + origin = Origin.MANUAL, + memo = null, + confirmed = true, + reviewCount = null, + longitude = 126.9246033, + latitude = 33.45241976, + openingHours = null, + ) + val endPlace = + Place( + id = LongTypeId(1L), + roomUid = UuidTypeId(UUID.randomUUID()), + scheduleId = LongTypeId(0L), + name = "", + url = null, + thumbnailLinks = ThumbnailLinks(listOf()), + address = null, + phoneNumber = null, + starGrade = null, + origin = Origin.MANUAL, + memo = null, + confirmed = true, + reviewCount = null, + longitude = 126.9041895, + latitude = 33.4048969, + openingHours = null, + ) - val distance = tmapNavigationClient.getDistance(start, end) + val distance = tmapNavigationClient.getDistance(startPlace, endPlace) println("distance = $distance") } }