feat(ranking): 스냅샷 스케줄러를 job 서비스에 연결한다
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotRefreshService
|
import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobService
|
||||||
import org.redisson.api.RedissonClient
|
import org.redisson.api.RedissonClient
|
||||||
import org.springframework.scheduling.annotation.Scheduled
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
import org.springframework.stereotype.Component
|
import org.springframework.stereotype.Component
|
||||||
@@ -8,7 +8,7 @@ import java.util.concurrent.TimeUnit
|
|||||||
|
|
||||||
@Component
|
@Component
|
||||||
class CreatorRankingSnapshotScheduler(
|
class CreatorRankingSnapshotScheduler(
|
||||||
private val refreshService: CreatorRankingSnapshotRefreshService,
|
private val jobService: CreatorRankingSnapshotJobService,
|
||||||
private val redissonClient: RedissonClient
|
private val redissonClient: RedissonClient
|
||||||
) {
|
) {
|
||||||
@Scheduled(cron = "0 30 7 * * MON", zone = "Asia/Seoul")
|
@Scheduled(cron = "0 30 7 * * MON", zone = "Asia/Seoul")
|
||||||
@@ -18,7 +18,7 @@ class CreatorRankingSnapshotScheduler(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (lock.tryLock(0, -1, TimeUnit.SECONDS)) {
|
if (lock.tryLock(0, -1, TimeUnit.SECONDS)) {
|
||||||
refreshService.refreshLastCompletedWeek()
|
jobService.refreshLastCompletedWeekByScheduledJob()
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
if (lock.isHeldByCurrentThread) {
|
if (lock.isHeldByCurrentThread) {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.v2.ranking.application.CreatorRankingSnapshotJobService
|
||||||
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.redisson.api.RLock
|
||||||
|
import org.redisson.api.RedissonClient
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class CreatorRankingSnapshotSchedulerTest {
|
||||||
|
@Test
|
||||||
|
@DisplayName("주간 스냅샷 스케줄러는 매주 월요일 07:30 KST cron으로 job 서비스를 호출한다")
|
||||||
|
fun shouldScheduleWeeklySnapshotRefreshAtKstMondaySevenThirty() {
|
||||||
|
val scheduled = CreatorRankingSnapshotScheduler::class.java
|
||||||
|
.getDeclaredMethod("refreshLastCompletedWeek")
|
||||||
|
.getAnnotation(Scheduled::class.java)
|
||||||
|
val service = Mockito.mock(CreatorRankingSnapshotJobService::class.java)
|
||||||
|
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
||||||
|
val lock = Mockito.mock(RLock::class.java)
|
||||||
|
Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock)
|
||||||
|
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true)
|
||||||
|
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true)
|
||||||
|
val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient)
|
||||||
|
|
||||||
|
scheduler.refreshLastCompletedWeek()
|
||||||
|
|
||||||
|
assertEquals("0 30 7 * * MON", scheduled.cron)
|
||||||
|
assertEquals("Asia/Seoul", scheduled.zone)
|
||||||
|
Mockito.verify(service).refreshLastCompletedWeekByScheduledJob()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("주간 스냅샷 스케줄러는 Redisson lock을 획득한 인스턴스만 갱신을 실행한다")
|
||||||
|
fun shouldRefreshLastCompletedWeekOnlyWhenRedissonLockAcquired() {
|
||||||
|
val service = Mockito.mock(CreatorRankingSnapshotJobService::class.java)
|
||||||
|
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
||||||
|
val lock = Mockito.mock(RLock::class.java)
|
||||||
|
Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock)
|
||||||
|
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true)
|
||||||
|
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true)
|
||||||
|
val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient)
|
||||||
|
|
||||||
|
scheduler.refreshLastCompletedWeek()
|
||||||
|
|
||||||
|
Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh")
|
||||||
|
Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS)
|
||||||
|
Mockito.verify(service).refreshLastCompletedWeekByScheduledJob()
|
||||||
|
Mockito.verify(lock).unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("주간 스냅샷 스케줄러는 Redisson lock 획득 실패 시 갱신을 건너뛴다")
|
||||||
|
fun shouldSkipLastCompletedWeekRefreshWhenRedissonLockNotAcquired() {
|
||||||
|
val service = Mockito.mock(CreatorRankingSnapshotJobService::class.java)
|
||||||
|
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
||||||
|
val lock = Mockito.mock(RLock::class.java)
|
||||||
|
Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock)
|
||||||
|
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(false)
|
||||||
|
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(false)
|
||||||
|
val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient)
|
||||||
|
|
||||||
|
scheduler.refreshLastCompletedWeek()
|
||||||
|
|
||||||
|
Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh")
|
||||||
|
Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS)
|
||||||
|
Mockito.verify(service, Mockito.never()).refreshLastCompletedWeekByScheduledJob()
|
||||||
|
Mockito.verify(lock, Mockito.never()).unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package kr.co.vividnext.sodalive.v2.ranking.application
|
package kr.co.vividnext.sodalive.v2.ranking.application
|
||||||
|
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.adapter.out.scheduler.CreatorRankingSnapshotScheduler
|
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingSnapshotCandidate
|
import kr.co.vividnext.sodalive.v2.ranking.domain.CreatorRankingSnapshotCandidate
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationPort
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationPort
|
||||||
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationResult
|
import kr.co.vividnext.sodalive.v2.ranking.port.out.CreatorRankingAggregationResult
|
||||||
@@ -11,17 +10,12 @@ import org.junit.jupiter.api.Assertions.assertThrows
|
|||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.extension.ExtendWith
|
import org.junit.jupiter.api.extension.ExtendWith
|
||||||
import org.mockito.Mockito
|
|
||||||
import org.redisson.api.RLock
|
|
||||||
import org.redisson.api.RedissonClient
|
|
||||||
import org.springframework.boot.test.system.CapturedOutput
|
import org.springframework.boot.test.system.CapturedOutput
|
||||||
import org.springframework.boot.test.system.OutputCaptureExtension
|
import org.springframework.boot.test.system.OutputCaptureExtension
|
||||||
import org.springframework.scheduling.annotation.Scheduled
|
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager
|
import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
@ExtendWith(OutputCaptureExtension::class)
|
@ExtendWith(OutputCaptureExtension::class)
|
||||||
class CreatorRankingSnapshotRefreshServiceTest {
|
class CreatorRankingSnapshotRefreshServiceTest {
|
||||||
@@ -168,65 +162,6 @@ class CreatorRankingSnapshotRefreshServiceTest {
|
|||||||
assertEquals(true, output.out.contains("error=aggregate failed"))
|
assertEquals(true, output.out.contains("error=aggregate failed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("주간 스냅샷 스케줄러는 매주 월요일 07:30 KST cron으로 갱신 서비스를 호출한다")
|
|
||||||
fun shouldScheduleWeeklySnapshotRefreshAtKstMondaySevenThirty() {
|
|
||||||
val scheduled = CreatorRankingSnapshotScheduler::class.java
|
|
||||||
.getDeclaredMethod("refreshLastCompletedWeek")
|
|
||||||
.getAnnotation(Scheduled::class.java)
|
|
||||||
val service = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java)
|
|
||||||
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
|
||||||
val lock = Mockito.mock(RLock::class.java)
|
|
||||||
Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock)
|
|
||||||
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true)
|
|
||||||
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true)
|
|
||||||
val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient)
|
|
||||||
|
|
||||||
scheduler.refreshLastCompletedWeek()
|
|
||||||
|
|
||||||
assertEquals("0 30 7 * * MON", scheduled.cron)
|
|
||||||
assertEquals("Asia/Seoul", scheduled.zone)
|
|
||||||
Mockito.verify(service).refreshLastCompletedWeek()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("주간 스냅샷 스케줄러는 Redisson lock을 획득한 인스턴스만 갱신을 실행한다")
|
|
||||||
fun shouldRefreshLastCompletedWeekOnlyWhenRedissonLockAcquired() {
|
|
||||||
val service = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java)
|
|
||||||
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
|
||||||
val lock = Mockito.mock(RLock::class.java)
|
|
||||||
Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock)
|
|
||||||
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(true)
|
|
||||||
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(true)
|
|
||||||
val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient)
|
|
||||||
|
|
||||||
scheduler.refreshLastCompletedWeek()
|
|
||||||
|
|
||||||
Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh")
|
|
||||||
Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS)
|
|
||||||
Mockito.verify(service).refreshLastCompletedWeek()
|
|
||||||
Mockito.verify(lock).unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("주간 스냅샷 스케줄러는 Redisson lock 획득 실패 시 갱신을 건너뛴다")
|
|
||||||
fun shouldSkipLastCompletedWeekRefreshWhenRedissonLockNotAcquired() {
|
|
||||||
val service = Mockito.mock(CreatorRankingSnapshotRefreshService::class.java)
|
|
||||||
val redissonClient = Mockito.mock(RedissonClient::class.java)
|
|
||||||
val lock = Mockito.mock(RLock::class.java)
|
|
||||||
Mockito.`when`(redissonClient.getLock("lock:creator-ranking-snapshot-refresh")).thenReturn(lock)
|
|
||||||
Mockito.`when`(lock.tryLock(0, -1, TimeUnit.SECONDS)).thenReturn(false)
|
|
||||||
Mockito.`when`(lock.isHeldByCurrentThread).thenReturn(false)
|
|
||||||
val scheduler = CreatorRankingSnapshotScheduler(service, redissonClient)
|
|
||||||
|
|
||||||
scheduler.refreshLastCompletedWeek()
|
|
||||||
|
|
||||||
Mockito.verify(redissonClient).getLock("lock:creator-ranking-snapshot-refresh")
|
|
||||||
Mockito.verify(lock).tryLock(0, -1, TimeUnit.SECONDS)
|
|
||||||
Mockito.verify(service, Mockito.never()).refreshLastCompletedWeek()
|
|
||||||
Mockito.verify(lock, Mockito.never()).unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun service(
|
private fun service(
|
||||||
aggregationPort: CreatorRankingAggregationPort = FakeCreatorRankingAggregationPort(),
|
aggregationPort: CreatorRankingAggregationPort = FakeCreatorRankingAggregationPort(),
|
||||||
snapshotPort: CreatorRankingSnapshotPort = FakeCreatorRankingSnapshotPort()
|
snapshotPort: CreatorRankingSnapshotPort = FakeCreatorRankingSnapshotPort()
|
||||||
|
|||||||
Reference in New Issue
Block a user