Skip to content

Commit

Permalink
Feat/137 global caching (#143)
Browse files Browse the repository at this point in the history
* chore: add redis container

* feat: redis caching

* chore: ecr update for performanceTest

* fix: typo

* docs: update open-api.yaml

* chore: rollback for merge

---------

Co-authored-by: junha-ahn <junha.ahn.dev@gmail.com>
Co-authored-by: Git Actions <no-reply@github.com>
  • Loading branch information
3 people authored Nov 19, 2023
1 parent e55633a commit b31095a
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 73 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-jdbc")
implementation("org.springframework.boot:spring-boot-starter-cache")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("com.ninja-squad:springmockk:4.0.2")
Expand Down
23 changes: 23 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,22 @@ services:
networks:
- spring-network

redis:
image: redis:latest
container_name: redis
ports:
- 6379:6379

labels:
- 'name=redis'
- 'mode=standalone'

restart: 'no'
command: redis-server

networks:
- spring-network

api:
container_name: api
build:
Expand All @@ -44,9 +60,16 @@ services:
JWT_ISSUER: minjun
QUEUE_SERVER_URL : http://localhost
SPRING_PROFILES_ACTIVE: local
REDIS_HOST: localhost
REDIS_PORT: 6379

depends_on:
db:
condition: service_healthy
redis:
condition: service_started


networks:
- spring-network

Expand Down
40 changes: 4 additions & 36 deletions docs/open-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ paths:
content:
'*/*':
schema:
$ref: '#/components/schemas/PageEvent'
type: object
/events/{id}:
get:
tags:
Expand Down Expand Up @@ -519,10 +519,10 @@ components:
pageSize:
type: integer
format: int32
unpaged:
type: boolean
paged:
type: boolean
unpaged:
type: boolean
Reservation:
required:
- address
Expand Down Expand Up @@ -613,43 +613,11 @@ components:
type: array
items:
$ref: '#/components/schemas/GrantedAuthority'
isAccountNonExpired:
type: boolean
isAccountNonLocked:
type: boolean
isCredentialsNonExpired:
type: boolean
PageEvent:
type: object
properties:
totalElements:
type: integer
format: int64
totalPages:
type: integer
format: int32
size:
type: integer
format: int32
content:
type: array
items:
$ref: '#/components/schemas/Event'
number:
type: integer
format: int32
sort:
$ref: '#/components/schemas/SortObject'
first:
type: boolean
last:
type: boolean
numberOfElements:
type: integer
format: int32
pageable:
$ref: '#/components/schemas/PageableObject'
empty:
isAccountNonExpired:
type: boolean
EventResponse:
required:
Expand Down
6 changes: 6 additions & 0 deletions src/integrationTest/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ management:
enabled: 'true'

spring:
cache:
type: redis
jpa:
generate-ddl: 'true'
hibernate:
Expand All @@ -16,6 +18,9 @@ spring:
web:
pageable:
one-indexed-parameters: true
redis:
host: localhost
port: 6379

ticketing:
jwt:
Expand All @@ -26,3 +31,4 @@ ticketing:
queue:
server:
url: http://localhost:8082/ticket

42 changes: 26 additions & 16 deletions src/main/kotlin/com/group4/ticketingservice/config/CacheConfig.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
package com.group4.ticketingservice.config

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import org.springframework.cache.CacheManager
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.EnableCaching
import org.springframework.cache.concurrent.ConcurrentMapCache
import org.springframework.cache.support.SimpleCacheManager
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.cache.RedisCacheManager.RedisCacheManagerBuilder
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializationContext
import java.time.Duration

@Configuration
@EnableCaching
class CacheConfig {
class CacheConfig(
private val redisConnectionFactory: RedisConnectionFactory
) {
companion object {
private const val EVENT_CACHE = "getEvents"
}

@Bean
fun cacheManager(): CacheManager {
val simpleCacheManager = SimpleCacheManager()
simpleCacheManager.setCaches(
listOf(
ConcurrentMapCache(EVENT_CACHE)
)
)
return simpleCacheManager
}
val objectMapper = ObjectMapper()
.findAndRegisterModules()
.enable(SerializationFeature.INDENT_OUTPUT)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerModules(JavaTimeModule())

@CacheEvict(allEntries = true, value = [EVENT_CACHE])
@Scheduled(fixedDelay = 10 * 60 * 1000, initialDelay = 500)
fun cacheEvict() {}
val builder = RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory)
val configuration: RedisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(GenericJackson2JsonRedisSerializer(objectMapper)))
.entryTtl(Duration.ofMinutes(30))
builder.cacheDefaults(configuration)
return builder.build()
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/com/group4/ticketingservice/config/RedisConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.group4.ticketingservice.config

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.RedisStandaloneConfiguration
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory

@Configuration
class RedisConfig {
@Value("\${spring.data.redis.host}")
private val host: String? = null

@Value("\${spring.data.redis.port}")
private val port = 0

@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
return LettuceConnectionFactory(
RedisStandaloneConfiguration(host!!, port)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ package com.group4.ticketingservice.controller

import com.group4.ticketingservice.dto.EventCreateRequest
import com.group4.ticketingservice.dto.EventResponse
import com.group4.ticketingservice.entity.Event
import com.group4.ticketingservice.service.EventService
import io.swagger.v3.oas.annotations.Hidden
import jakarta.servlet.http.HttpServletRequest
import jakarta.validation.Valid
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort
import org.springframework.data.web.PageableDefault
Expand Down Expand Up @@ -83,12 +81,16 @@ class EventController @Autowired constructor(
}

@GetMapping
@Cacheable(value = ["getEvents"], key = "#pageable.pageNumber+'-'+#pageable.pageSize+'-'+#pageable.sort.toString()+'-'+#name")
@Cacheable(
value = ["getEvents"],
key = "#pageable.pageNumber+'-'+#pageable.pageSize+'-'+#pageable.sort.toString()+'-'+#name",
cacheManager = "cacheManager"
)
fun getEvents(
request: HttpServletRequest,
@RequestParam(required = false) name: String?,
@PageableDefault(size = 10, sort = ["id"], direction = Sort.Direction.DESC) pageable: Pageable
): ResponseEntity<Page<Event>> {
): Any {
val page = eventService.getEvents(name, pageable)
print(pageable.sort.toString())
val headers = HttpHeaders()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.group4.ticketingservice.service

import com.group4.ticketingservice.dto.EventResponse
import com.group4.ticketingservice.dto.EventSpecifications
import com.group4.ticketingservice.entity.Event
import com.group4.ticketingservice.repository.EventRepository
Expand Down Expand Up @@ -36,8 +37,20 @@ class EventService(
return eventRepository.findById(id).orElse(null)
}

fun getEvents(name: String?, pageable: Pageable): Page<Event> {
fun getEvents(name: String?, pageable: Pageable): Page<EventResponse> {
val specification = EventSpecifications.withName(name)
return PageImpl(eventRepository.findAllBy(specification, pageable))
return PageImpl(
eventRepository.findAllBy(specification, pageable).map {
EventResponse(
id = it.id!!,
name = it.name,
startDate = it.startDate,
endDate = it.endDate,
reservationStartTime = it.reservationStartTime,
reservationEndTime = it.reservationEndTime,
maxAttendees = it.maxAttendees
)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package com.group4.ticketingservice.utils

import com.group4.ticketingservice.controller.HealthController
import com.group4.ticketingservice.dto.ErrorResponseDTO
import com.group4.ticketingservice.dto.EventResponse
import com.group4.ticketingservice.dto.ReservationResponse
import com.group4.ticketingservice.dto.SuccessResponseDTO
import com.group4.ticketingservice.entity.Event
import com.group4.ticketingservice.entity.Reservation
import org.springframework.beans.factory.annotation.Value
import org.springframework.core.MethodParameter
Expand Down Expand Up @@ -56,6 +54,13 @@ class ResponseAdvice<T>(
path = response.headers.getFirst("Content-Location")
) as T?
}
if (body is LinkedHashMap<*, *>) { // cached data
val b = body["body"] as LinkedHashMap<String, Any>
return SuccessResponseDTO(
data = b["content"],
path = request.uri.path
) as T?
}

if (body is ErrorResponseDTO) {
return body
Expand Down Expand Up @@ -96,18 +101,6 @@ class ResponseAdvice<T>(

if (data.isEmpty()) {
data = listOf()
} else if (data[0] is Event) {
data = (page.content as List<Event>).map {
EventResponse(
id = it.id!!,
name = it.name,
startDate = it.startDate,
endDate = it.endDate,
reservationStartTime = it.reservationStartTime,
reservationEndTime = it.reservationEndTime,
maxAttendees = it.maxAttendees
)
}
} else if (data[0] is Reservation) {
data = (page.content as List<Reservation>).map {
ReservationResponse(
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ spring:
resources:
add-mappings: 'false'

cache:
type: redis

mvc:
log-resolved-exception: 'false'
Expand All @@ -20,6 +22,10 @@ spring:
web:
pageable:
one-indexed-parameters: true
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}



datasource:
Expand Down
Loading

0 comments on commit b31095a

Please sign in to comment.