Skip to content

Commit

Permalink
feat: 구독 기능 트랜잭션 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
van1164 committed Jun 6, 2024
1 parent fcdc188 commit ef78405
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 22 deletions.
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ subprojects {
annotationProcessor("org.projectlombok:lombok")

testImplementation("io.projectreactor:reactor-test")

implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("com.github.jasync-sql:jasync-r2dbc-mysql:2.2.0")
implementation("io.asyncer:r2dbc-mysql:1.1.0")

implementation("net.bramp.ffmpeg:ffmpeg:0.8.0")

implementation("io.github.microutils:kotlin-logging:1.12.0")
Expand Down
8 changes: 8 additions & 0 deletions user/src/main/kotlin/com/van1164/user/config/UserConfig.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
package com.van1164.user.config

import com.van1164.user.UserRepository
import com.van1164.user.subscribe.repository.SubscribeRepository
import org.springframework.context.annotation.Configuration
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories


@Configuration
@EnableR2dbcRepositories(basePackageClasses = [SubscribeRepository::class,UserRepository::class])
class UserConfig {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@ package com.van1164.user.subscribe.service

import com.van1164.common.domain.UserSubscribe
import com.van1164.common.exception.AlreadySubscribeException
import com.van1164.common.redis.RedisService
import com.van1164.user.UserRepository
import com.van1164.user.subscribe.repository.SubscribeRepository
import org.springframework.data.crossstore.ChangeSetPersister.NotFoundException
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Service
import org.springframework.transaction.TransactionManager
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.reactive.TransactionalOperator
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.switchIfEmpty
import reactor.kotlin.core.publisher.toMono
import java.lang.Exception

@Service
@EnableR2dbcRepositories(basePackageClasses = [SubscribeRepository::class,UserRepository::class])
class SubscribeService(
private val subscribeRepository: SubscribeRepository,
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val redisService: RedisService,
private val transactionalOperator: TransactionalOperator,
) {

@Transactional
Expand All @@ -34,32 +42,39 @@ class SubscribeService(
.map{
UserSubscribe(fromUserId,toUserId)
}
.flatMap(subscribeRepository::save)
.flatMap {
subscribeRepository.save(it)
redisService.increaseSubscribe(it.toUserId)
}
.map {
ResponseEntity.ok().body("success")
}
.`as`(transactionalOperator::transactional)
.onErrorResume {
when(it){
is NotFoundException -> ResponseEntity.badRequest().body("존재하지 않는 User").toMono()
is AlreadySubscribeException -> ResponseEntity.badRequest().body("이미 구독 중").toMono()
else -> ResponseEntity.badRequest().body("fail").toMono()
else -> ResponseEntity.internalServerError().body("fail").toMono()
}
}
}

@Transactional
fun subscribeCancel(fromUserId: String, toUserId: String): Mono<ResponseEntity<String>> {
return subscribeRepository.findByFromUserIdAndToUserId(fromUserId,toUserId)
.doOnNext {
println(it)
}
.switchIfEmpty { Mono.error(NotFoundException()) }
.flatMap {
subscribeRepository.delete(it)
}
.then(Mono.defer { redisService.decreaseSubscribe(toUserId) })
.thenReturn(ResponseEntity.ok().body("success"))
.defaultIfEmpty(ResponseEntity.badRequest().body("존재하지 않는 User"))
.onErrorReturn(ResponseEntity.badRequest().body("fail"))
.switchIfEmpty(Mono.error(Exception()))
.onErrorResume {
when(it){
is NotFoundException -> ResponseEntity.badRequest().body("구독중이 아닙니다.").toMono()
else -> ResponseEntity.internalServerError().body("fail").toMono()
}
}

}
}
29 changes: 28 additions & 1 deletion util/src/main/kotlin/com/van1164/common/config/R2dbcConfig.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
package com.van1164.common.config

class R2dbcConfig {
import io.r2dbc.spi.ConnectionFactory
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
import org.springframework.r2dbc.connection.R2dbcTransactionManager
import org.springframework.transaction.ReactiveTransactionManager
import org.springframework.transaction.annotation.EnableTransactionManagement
import org.springframework.transaction.annotation.TransactionManagementConfigurer
import org.springframework.transaction.reactive.TransactionalOperator


@Configuration
@EnableR2dbcRepositories
@EnableTransactionManagement
class R2dbcConfig(private val connectionFactory: ConnectionFactory) : TransactionManagementConfigurer {
@Bean
fun transactionManager(): ReactiveTransactionManager {
return R2dbcTransactionManager(connectionFactory)
}

@Bean
fun transactionalOperator(transactionManager: ReactiveTransactionManager): TransactionalOperator {
return TransactionalOperator.create(transactionManager)
}

override fun annotationDrivenTransactionManager(): ReactiveTransactionManager {
return transactionManager()
}
}
80 changes: 68 additions & 12 deletions video/src/test/kotlin/service/SubscribeServiceTest.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package service

import com.van1164.common.redis.RedisRepository
import com.van1164.common.test.Transaction
import com.van1164.user.subscribe.service.SubscribeService
import com.van1164.util.SUBSCRIBE_PREFIX
import com.van1164.video.VideoApplication
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.HttpStatus
import org.springframework.test.annotation.Rollback
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
Expand All @@ -17,14 +23,27 @@ import kotlin.test.assertNotNull
@SpringBootTest(classes = [VideoApplication::class])
class SubscribeServiceTest @Autowired constructor(
val subscribeService: SubscribeService,
val transaction: Transaction
val transaction: Transaction,
val redisRepository: RedisRepository,
) {

val testEmail = "[email protected]"

@BeforeEach
fun beforeEach(){
redisRepository.remove(SUBSCRIBE_PREFIX + testEmail).block()
}

@AfterEach
fun afterEach(){
redisRepository.remove(SUBSCRIBE_PREFIX + testEmail).block()
}

@Test
@DisplayName("구독 성공 테스트")
@Rollback(value = true)
fun successTest(){
subscribeService.subscribe("[email protected]","[email protected]")
subscribeService.subscribe(testEmail,testEmail)
.`as`(transaction::withRollback)
.`as`(StepVerifier::create)
.assertNext{response->
Expand All @@ -33,14 +52,23 @@ class SubscribeServiceTest @Autowired constructor(
assertNotNull(response.body)
assertEquals(response.body,"success")
}
.verifyComplete()

redisRepository.load(SUBSCRIBE_PREFIX+testEmail)
.defaultIfEmpty("X")
.`as`(StepVerifier::create)
.assertNext{
assertEquals(it,"1")
}
.verifyComplete()

}

@Test
@DisplayName("없는 아이디로 인한 구독 실패 테스트")
@Rollback(value = true)
fun noUserIdFailTest(){
subscribeService.subscribe("[email protected]","[email protected]")
subscribeService.subscribe("[email protected]",testEmail)
.`as`(transaction::withRollback)
.`as`(StepVerifier::create)
.assertNext{response->
Expand All @@ -49,14 +77,25 @@ class SubscribeServiceTest @Autowired constructor(
assertNotNull(response.body)
assertEquals(response.body ,"존재하지 않는 User")
}
.verifyComplete()

redisRepository.load(SUBSCRIBE_PREFIX+testEmail)
.defaultIfEmpty("X")
.`as`(StepVerifier::create)
.assertNext{
assertEquals(it,"X")
}
.verifyComplete()
}

@Test
@DisplayName("이미 구독한 사용자 실패 테스트")
@Rollback(value = true)
fun duplicatedSubscribeFailTest(){
subscribeService.subscribe("[email protected]","[email protected]").`as`(transaction::withRollback).block()
subscribeService.subscribe("[email protected]","[email protected]")
subscribeService.subscribe(testEmail,testEmail)
.flatMap {
subscribeService.subscribe(testEmail,testEmail)
}
.`as`(transaction::withRollback)
.`as`(StepVerifier::create)
.assertNext{response->
Expand All @@ -65,24 +104,31 @@ class SubscribeServiceTest @Autowired constructor(
assertNotNull(response.body)
assertEquals(response.body ,"이미 구독 중")
}
.verifyComplete()

redisRepository.load(SUBSCRIBE_PREFIX+testEmail)
.defaultIfEmpty("X")
.`as`(StepVerifier::create)
.assertNext{
assertEquals(it,"1")
}
.verifyComplete()
}

@Test
@DisplayName("구독 취소 성공 테스트")
@Rollback(value = true)
fun subscribeCancel(){
subscribeService.subscribe("[email protected]","[email protected]")
.`as`(transaction::withRollback)
.`as`(StepVerifier::create)
.assertNext { response ->
subscribeService.subscribe(testEmail,testEmail)
.doOnNext{ response ->
assertNotNull(response)
assertEquals(response.statusCode, HttpStatus.OK)
assertNotNull(response.body)
assertEquals(response.body, "success")
}


subscribeService.subscribeCancel("[email protected]","[email protected]")
.flatMap {
subscribeService.subscribeCancel(testEmail,testEmail)
}
.`as`(transaction::withRollback)
.`as`(StepVerifier::create)
.assertNext{cancel->
Expand All @@ -91,6 +137,16 @@ class SubscribeServiceTest @Autowired constructor(
assertNotNull(cancel.body)
assertEquals(cancel.body ,"success")
}
.verifyComplete()


redisRepository.load(SUBSCRIBE_PREFIX+testEmail)
.`as`(StepVerifier::create)
.assertNext{
assertEquals(it,"0")
}
.verifyComplete()

}

}

0 comments on commit ef78405

Please sign in to comment.