-
Notifications
You must be signed in to change notification settings - Fork 0
[YS-299] feat: 지역별 공고 개수 조회 UseCase Output 캐싱 적용 및 Redis 테스트 컨테이너 추가 #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
81a06f6
3e64ca7
af4a8b8
284dd5b
5a64aef
03691f8
3e07be7
f536112
b43e40f
9d8405b
e1cdb77
7885f28
d4c6b13
3a722d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,7 +10,10 @@ import com.dobby.backend.application.usecase.experiment.GetMyExperimentPostsUseC | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.dobby.backend.domain.exception.ExperimentAreaInCorrectException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.dobby.backend.domain.exception.ExperimentAreaOverflowException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.dobby.backend.domain.exception.InvalidRequestValueException | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.dobby.backend.domain.gateway.CacheGateway | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Area | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.dobby.backend.infrastructure.database.entity.enums.experiment.RecruitStatus | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.fasterxml.jackson.databind.ObjectMapper | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.transaction.Transactional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,9 +33,12 @@ class ExperimentPostService( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val getExperimentPostTotalCountByCustomFilterUseCase: GetExperimentPostTotalCountByCustomFilterUseCase, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val getMyExperimentPostsUseCase: GetMyExperimentPostsUseCase, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val getMyExperimentPostTotalCountUseCase: GetMyExperimentPostTotalCountUseCase, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val cacheGateway: CacheGateway, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private val objectMapper: ObjectMapper, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun createNewExperimentPost(input: CreateExperimentPostUseCase.Input): CreateExperimentPostUseCase.Output { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evictExperimentPostCountsCaches() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return createExperimentPostUseCase.execute(input) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -69,14 +75,36 @@ class ExperimentPostService( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun getExperimentPostCounts(input: Any): Any { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return when (input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| getCachedExperimentPostCounts(input.recruitStatus)?.let { return it } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val output = when (input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is GetExperimentPostCountsByRegionUseCase.Input -> getExperimentPostCountsByRegionUseCase.execute(input) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| is GetExperimentPostCountsByAreaUseCase.Input -> getExperimentPostCountsByAreaUseCase.execute(input) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else -> throw IllegalArgumentException("Invalid input type: ${input::class.java.simpleName}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheExperimentPostCounts(input.recruitStatus, output) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
77
to
+93
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ObjectMapper 예외 처리가 필요합니다.
다음과 같이 예외 처리를 추가해주세요: if (input is GetExperimentPostCountsByRegionUseCase.Input && input.region == null) {
- cacheExperimentPostCounts(input.recruitStatus, output)
+ try {
+ cacheExperimentPostCounts(input.recruitStatus, output)
+ } catch (e: Exception) {
+ log.error("Failed to cache experiment post counts: ${e.message}")
+ }
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private fun getCachedExperimentPostCounts(recruitStatus: RecruitStatus): GetExperimentPostCountsByRegionUseCase.Output? { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val cacheKey = "experimentPostCounts:$recruitStatus" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return cacheGateway.get(cacheKey)?.let { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| objectMapper.readValue(it, GetExperimentPostCountsByRegionUseCase.Output::class.java) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private fun cacheExperimentPostCounts(recruitStatus: RecruitStatus, output: Any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val cacheKey = "experimentPostCounts:$recruitStatus" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cacheGateway.set(cacheKey, objectMapper.writeValueAsString(output)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+95
to
105
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 직렬화/역직렬화 예외 처리가 필요합니다. ObjectMapper 작업 시 발생할 수 있는 예외에 대한 처리가 필요합니다. private fun getCachedExperimentPostCounts(recruitStatus: RecruitStatus): GetExperimentPostCountsByRegionUseCase.Output? {
val cacheKey = "experimentPostCounts:$recruitStatus"
return cacheGateway.get(cacheKey)?.let {
+ try {
objectMapper.readValue(it, GetExperimentPostCountsByRegionUseCase.Output::class.java)
+ } catch (e: Exception) {
+ log.error("Failed to deserialize cached value: ${e.message}")
+ null
+ }
}
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun validateFilter(input: GetExperimentPostsUseCase.Input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private fun validateFilter(input: GetExperimentPostsUseCase.Input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오 제가 이 부분 놓친 것 같은데 바꾸어주셔서 감사합니다! 🙇♀️ |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| val locationInfo = input.customFilter.locationTarget ?: return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| locationInfo.areas?.let { validateLocationAreaCount(it) } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -123,5 +151,10 @@ class ExperimentPostService( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Transactional | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fun deleteExperimentPost(input: DeleteExperimentPostUseCase.Input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| deleteExperimentPostUseCase.execute(input) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| evictExperimentPostCountsCaches() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private fun evictExperimentPostCountsCaches() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| listOf("ALL", "OPEN").forEach { cacheGateway.evict("experimentPostCounts:$it") } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package com.dobby.backend.domain.gateway | ||
|
|
||
| interface CacheGateway { | ||
| fun get(key: String): String? | ||
| fun set(key: String, value: String) | ||
| fun evict(key: String) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||||||||||||||||
| package com.dobby.backend.infrastructure.gateway.cache | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import com.dobby.backend.domain.gateway.CacheGateway | ||||||||||||||||||||||||
| import org.springframework.core.env.Environment | ||||||||||||||||||||||||
| import org.springframework.data.redis.core.RedisTemplate | ||||||||||||||||||||||||
| import org.springframework.stereotype.Component | ||||||||||||||||||||||||
| import java.util.concurrent.TimeUnit | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||
| class RedisCacheGatewayImpl( | ||||||||||||||||||||||||
| private val redisTemplate: RedisTemplate<String, String>, | ||||||||||||||||||||||||
| private val environment: Environment | ||||||||||||||||||||||||
| ) : CacheGateway { | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private val cacheTimeout = 240L | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| override fun get(key: String): String? { | ||||||||||||||||||||||||
| return redisTemplate.opsForValue().get(getCacheKey(key)) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Redis 작업 실패 시 예외 처리 추가 필요 Redis 작업 실패 시 적절한 예외 처리를 추가하면 장애 상황에서도 안정적으로 동작할 수 있습니다. override fun get(key: String): String? {
- return redisTemplate.opsForValue().get(getCacheKey(key))
+ return try {
+ redisTemplate.opsForValue().get(getCacheKey(key))
+ } catch (e: Exception) {
+ logger.error("캐시 조회 실패. key={}", key, e)
+ null
+ }
}📝 Committable suggestion
Suggested change
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 라비따이야, 레디스에 캐시가 없을 수도 있지. 안그러니? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Ji-soo708 네, 말씀하신대로입니다. 레디스에 캐시가 없는 것은 정상적인 상황이므로 현재 구현처럼 단순히 null을 반환하는 것이 더 적절합니다. 제가 제안했던 에러 로깅은 불필요했네요. 이전 제안을 철회하겠습니다. ✏️ Learnings added
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| override fun set(key: String, value: String) { | ||||||||||||||||||||||||
| redisTemplate.opsForValue().set(getCacheKey(key), value, cacheTimeout, TimeUnit.MINUTES) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| override fun evict(key: String) { | ||||||||||||||||||||||||
| redisTemplate.delete(getCacheKey(key)) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private fun getCacheKey(key: String): String { | ||||||||||||||||||||||||
| val activeProfile = environment.activeProfiles.firstOrNull() ?: "local" | ||||||||||||||||||||||||
| return "$activeProfile:$key" | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| package com.dobby.backend.application.service | ||
|
|
||
| import com.dobby.backend.application.usecase.experiment.CreateExperimentPostUseCase | ||
| import com.dobby.backend.application.usecase.experiment.GetExperimentPostCountsByRegionUseCase | ||
| import com.dobby.backend.config.RedisTestContainer | ||
| import com.dobby.backend.domain.gateway.CacheGateway | ||
| import com.dobby.backend.infrastructure.database.entity.enums.MatchType | ||
| import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Area | ||
| import com.dobby.backend.infrastructure.database.entity.enums.areaInfo.Region | ||
| import com.dobby.backend.infrastructure.database.entity.enums.experiment.RecruitStatus | ||
| import com.dobby.backend.infrastructure.database.entity.enums.experiment.TimeSlot | ||
| import com.dobby.backend.infrastructure.database.entity.enums.member.GenderType | ||
| import com.dobby.backend.infrastructure.database.entity.enums.member.MemberStatus | ||
| import com.dobby.backend.infrastructure.database.entity.enums.member.ProviderType | ||
| import com.dobby.backend.infrastructure.database.entity.enums.member.RoleType | ||
| import com.dobby.backend.infrastructure.database.entity.member.MemberEntity | ||
| import com.dobby.backend.infrastructure.database.repository.MemberRepository | ||
| import com.fasterxml.jackson.databind.ObjectMapper | ||
| import io.kotest.core.spec.style.BehaviorSpec | ||
| import io.kotest.matchers.shouldBe | ||
| import io.kotest.matchers.nulls.shouldBeNull | ||
| import io.mockk.* | ||
| import org.springframework.beans.factory.annotation.Autowired | ||
| import org.springframework.boot.test.context.SpringBootTest | ||
| import org.springframework.test.context.ActiveProfiles | ||
| import java.time.LocalDate | ||
| import java.time.LocalDateTime | ||
|
|
||
| @SpringBootTest | ||
| @ActiveProfiles("test") | ||
| class ExperimentPostServiceTest @Autowired constructor( | ||
| private val experimentPostService: ExperimentPostService, | ||
| private val memberRepository: MemberRepository, | ||
| private val cacheGateway: CacheGateway, | ||
| private val objectMapper: ObjectMapper | ||
| ) : BehaviorSpec({ | ||
|
|
||
| val createExperimentPostUseCase = mockk<CreateExperimentPostUseCase>() | ||
| lateinit var memberId: String | ||
|
|
||
| listeners(RedisTestContainer) | ||
|
|
||
| beforeSpec { | ||
| clearAllMocks() | ||
|
|
||
| val savedMember = memberRepository.save( | ||
| MemberEntity( | ||
| id = "1", | ||
| oauthEmail = "test@gmail.com", | ||
| contactEmail = "test@gmail.com", | ||
| provider = ProviderType.GOOGLE, | ||
| status = MemberStatus.ACTIVE, | ||
| role = RoleType.RESEARCHER, | ||
| name = "테스트", | ||
| createdAt = LocalDateTime.now(), | ||
| updatedAt = LocalDateTime.now(), | ||
| deletedAt = null | ||
| ) | ||
| ) | ||
| memberId = savedMember.id | ||
| } | ||
|
|
||
| given("Redis 캐시에 지역별 실험 공고 개수를 저장할 때") { | ||
| val input = GetExperimentPostCountsByRegionUseCase.Input(region = null, recruitStatus = RecruitStatus.ALL) | ||
| val output = GetExperimentPostCountsByRegionUseCase.Output(total = 100, area = emptyList()) | ||
|
|
||
| val cacheKey = "experimentPostCounts:ALL" | ||
|
|
||
| `when`("캐시에 데이터를 저장하고 조회하면") { | ||
| cacheGateway.set(cacheKey, objectMapper.writeValueAsString(output)) | ||
| val cachedData = cacheGateway.get(cacheKey) | ||
|
|
||
| then("저장된 데이터가 올바르게 반환되어야 한다") { | ||
| cachedData shouldBe objectMapper.writeValueAsString(output) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| given("새로운 실험 공고를 생성할 때") { | ||
| val cacheKey = "experimentPostCounts:ALL" | ||
| cacheGateway.set(cacheKey, objectMapper.writeValueAsString(GetExperimentPostCountsByRegionUseCase.Output(100, emptyList()))) | ||
|
|
||
| val mockInput = mockk<CreateExperimentPostUseCase.Input>(relaxed = true) | ||
|
|
||
| every { mockInput.memberId } returns memberId | ||
| every { mockInput.leadResearcher } returns "야뿌 연구 리더" | ||
| every { mockInput.univName } returns "야뿌대학교" | ||
| every { mockInput.region } returns Region.SEOUL | ||
| every { mockInput.area } returns Area.GWANAKGU | ||
| every { mockInput.detailedAddress } returns "야뿌 연구소" | ||
| every { mockInput.reward } returns "Gift Card" | ||
| every { mockInput.title } returns "새로운 실험" | ||
| every { mockInput.content } returns "참여해 참여해" | ||
| every { mockInput.alarmAgree } returns true | ||
| every { mockInput.startDate } returns LocalDate.of(2024, 6, 1) | ||
| every { mockInput.endDate } returns LocalDate.of(2024, 6, 30) | ||
| every { mockInput.matchType } returns MatchType.OFFLINE | ||
| every { mockInput.count } returns 3 | ||
| every { mockInput.timeRequired } returns TimeSlot.ABOUT_1H | ||
|
|
||
| val mockTargetGroupInfo = mockk<CreateExperimentPostUseCase.TargetGroupInfo>(relaxed = true) | ||
| every { mockTargetGroupInfo.startAge } returns 20 | ||
| every { mockTargetGroupInfo.endAge } returns 30 | ||
| every { mockTargetGroupInfo.genderType } returns GenderType.MALE | ||
| every { mockTargetGroupInfo.otherCondition } returns "Healthy" | ||
|
|
||
| every { mockInput.targetGroupInfo } returns mockTargetGroupInfo | ||
|
|
||
| val mockApplyMethodInfo = mockk<CreateExperimentPostUseCase.ApplyMethodInfo>(relaxed = true) | ||
| every { mockApplyMethodInfo.content } returns "이메일로 연락주세요" | ||
| every { mockApplyMethodInfo.formUrl } returns "https://test.com/apply" | ||
| every { mockApplyMethodInfo.phoneNum } returns "010-1234-5678" | ||
|
|
||
| every { mockInput.applyMethodInfo } returns mockApplyMethodInfo | ||
|
|
||
| val mockImageListInfo = mockk<CreateExperimentPostUseCase.ImageListInfo>(relaxed = true) | ||
| every { mockImageListInfo.images } returns listOf("https://test.com/image1.jpg", "https://test.com/image2.jpg") | ||
| every { mockInput.imageListInfo } returns mockImageListInfo | ||
|
|
||
| every { createExperimentPostUseCase.execute(any()) } returns mockk() | ||
|
|
||
| `when`("게시글을 생성하면") { | ||
| experimentPostService.createNewExperimentPost(mockInput) | ||
|
|
||
| then("캐시가 삭제되어야 한다") { | ||
| val cachedData = cacheGateway.get(cacheKey) | ||
| cachedData.shouldBeNull() | ||
| } | ||
| } | ||
| } | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.dobby.backend.config | ||
|
|
||
| import io.kotest.core.listeners.TestListener | ||
| import io.kotest.core.spec.Spec | ||
| import org.testcontainers.containers.GenericContainer | ||
| import org.testcontainers.utility.DockerImageName | ||
|
|
||
| object RedisTestContainer : TestListener { | ||
| private val isCI = System.getenv("GITHUB_ACTIONS") == "true" | ||
|
|
||
| private val redis: GenericContainer<*> = GenericContainer(DockerImageName.parse("redis:7.0.8-alpine")).apply { | ||
| withExposedPorts(6379) | ||
| withReuse(true) | ||
|
|
||
| if (!isCI) { | ||
| portBindings = listOf("6379:6379") | ||
| } | ||
| } | ||
|
|
||
| override suspend fun beforeSpec(spec: Spec) { | ||
| if (!redis.isRunning) redis.start() | ||
|
|
||
| val redisHost = "127.0.0.1" | ||
| val redisPort = redis.getMappedPort(6379).toString() | ||
|
|
||
| System.setProperty("spring.data.redis.host", redisHost) | ||
| System.setProperty("spring.data.redis.port", redisPort) | ||
| } | ||
|
|
||
| override suspend fun afterSpec(spec: Spec) { | ||
| if (!isCI) { | ||
| redis.stop() | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재는 전체 지역의 공고 개수 조회
(region == null)가 가장 빈번하게 호출될 것으로 예상되어 이 경우에만 캐싱을 적용했습니다. 만약 특정 지역에 대한 조회 요청도 증가하는 것이 확인된다면, 해당 부분에 대한 캐싱 정책도 추가로 검토할 예정입니다~