Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions coupon-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("org.redisson:redisson-spring-boot-starter:3.25.0")

// Cache
implementation("com.github.ben-manes.caffeine:caffeine")

// Monitoring
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("io.micrometer:micrometer-registry-prometheus")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main.kotlin.com.woong2e.couponsystem.coupon.application.service.impl
import main.kotlin.com.woong2e.couponsystem.coupon.application.port.out.CouponIssueEventPublisher
import main.kotlin.com.woong2e.couponsystem.coupon.application.response.CouponIssueResponse
import main.kotlin.com.woong2e.couponsystem.coupon.application.service.CouponIssueService
import main.kotlin.com.woong2e.couponsystem.coupon.infra.cache.LocalCouponCacheManager
import main.kotlin.com.woong2e.couponsystem.coupon.infra.redis.CouponRedisRepository
import main.kotlin.com.woong2e.couponsystem.coupon.status.CouponErrorStatus
import main.kotlin.com.woong2e.couponsystem.global.exception.CustomException
Expand All @@ -13,19 +14,28 @@ import java.util.UUID
@Service("asyncLua")
class AsyncLuaCouponIssueService(
private val couponRedisRepository: CouponRedisRepository,
private val couponIssueEventPublisher: CouponIssueEventPublisher
private val couponIssueEventPublisher: CouponIssueEventPublisher,
private val localCouponCacheManager: LocalCouponCacheManager,
) : CouponIssueService {

override fun issue(couponId: UUID, userId: Long): CouponIssueResponse {
val result = couponRedisRepository.issueRequest(couponId.toString(), userId)
val key = couponId.toString()

when (result) {
"DUPLICATED" -> throw CustomException(CouponErrorStatus.COUPON_ALREADY_ISSUED)
"SOLD_OUT" -> throw CustomException(CouponErrorStatus.COUPON_OUT_OF_STOCK)
if (localCouponCacheManager.isSoldOut(key)) {
throw CustomException(CouponErrorStatus.COUPON_OUT_OF_STOCK)
}

val result = couponRedisRepository.issueRequest(key, userId)

return when (result) {
"SUCCESS" -> {
couponIssueEventPublisher.publish(couponId, userId)

return CouponIssueResponse.asyncIssued()
CouponIssueResponse.asyncIssued()
}
"DUPLICATED" -> throw CustomException(CouponErrorStatus.COUPON_ALREADY_ISSUED)
"SOLD_OUT" -> {
localCouponCacheManager.putSoldOut(key)
throw CustomException(CouponErrorStatus.COUPON_OUT_OF_STOCK)
}
else -> throw CustomException(ErrorStatus.SYSTEM_BUSY)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main.kotlin.com.woong2e.couponsystem.coupon.infra.cache

import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit

@Component
class LocalCouponCacheManager {

private val cache: Cache<String, Boolean> = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) // 10분 후 만료
.maximumSize(1000) // 최대 1000개 쿠폰 키 보관
.build()

fun putSoldOut(couponId: String) {
cache.put(couponId, true)
}

fun isSoldOut(couponId: String): Boolean {
return cache.getIfPresent(couponId) == true
}
}