diff --git a/build.gradle b/build.gradle index 228a285..5c66b32 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'io.springfox:springfox-boot-starter:3.0.0' + implementation 'org.redisson:redisson-spring-boot-starter:3.17.0' implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}" compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/hexagonal/coupon/adapter/out/persistence/CouponPersistenceAdapter.java b/src/main/java/com/hexagonal/coupon/adapter/out/persistence/CouponPersistenceAdapter.java index 609085c..4d09acf 100644 --- a/src/main/java/com/hexagonal/coupon/adapter/out/persistence/CouponPersistenceAdapter.java +++ b/src/main/java/com/hexagonal/coupon/adapter/out/persistence/CouponPersistenceAdapter.java @@ -22,7 +22,7 @@ class CouponPersistenceAdapter implements LoadCouponPort, CreateCouponPort, Upda @Override public Coupon loadCoupon(Long couponId) { - CouponJpaEntity couponJpaEntity = couponRepository.findLockById(couponId) + CouponJpaEntity couponJpaEntity = couponRepository.findById(couponId) .orElseThrow(CouponNotExistException::new); return couponMapper.mapToDomainEntity(couponJpaEntity); } diff --git a/src/main/java/com/hexagonal/coupon/application/service/CreateMemberCouponService.java b/src/main/java/com/hexagonal/coupon/application/service/CreateMemberCouponService.java index 753a193..07a9fb9 100644 --- a/src/main/java/com/hexagonal/coupon/application/service/CreateMemberCouponService.java +++ b/src/main/java/com/hexagonal/coupon/application/service/CreateMemberCouponService.java @@ -11,9 +11,14 @@ import com.hexagonal.coupon.common.exception.DuplicateCouponException; import com.hexagonal.coupon.domain.MemberCoupon; import lombok.RequiredArgsConstructor; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.concurrent.TimeUnit; + @Service @Transactional @RequiredArgsConstructor @@ -23,20 +28,38 @@ class CreateMemberCouponService implements CreateMemberCouponUseCase { private final CreateMemberCouponPort createMemberCouponPort; private final UpdateCouponStatePort updateCouponStatePort; private final FindCouponOfMemberPort findCouponOfMemberPort; + private final RedissonClient redissonClient; + + @Value("${redis.lock.coupon}") + private String couponLockName; @Override public CreateMemberCouponResponse createMemberCoupon(CreateMemberCouponCommand command) { - if (isNotStock(command.getCouponId())) { - throw new CouponNotRemainException(); - } + RLock lock = redissonClient.getLock(couponLockName); - if (isDuplicateCoupon(command)) { - throw new DuplicateCouponException(); - } + try { + if (!lock.tryLock(10, 3, TimeUnit.SECONDS)) { + throw new RuntimeException("Lock 획득 실패"); + } - updateCouponStatePort.decreaseRemainQuantity(command.getCouponId()); - MemberCoupon memberCoupon = createMemberCouponPort.createMemberCoupon(command.getMemberId(), command.getCouponId()); - return new CreateMemberCouponResponse(memberCoupon.getId(), command.getCouponId(), memberCoupon.getCreateDateTime()); + if (isNotStock(command.getCouponId())) { + throw new CouponNotRemainException(); + } + + if (isDuplicateCoupon(command)) { + throw new DuplicateCouponException(); + } + + updateCouponStatePort.decreaseRemainQuantity(command.getCouponId()); + MemberCoupon memberCoupon = createMemberCouponPort.createMemberCoupon(command.getMemberId(), command.getCouponId()); + return new CreateMemberCouponResponse(memberCoupon.getId(), command.getCouponId(), memberCoupon.getCreateDateTime()); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } finally { + if (lock != null && lock.isLocked()) { + lock.unlock(); + } + } } private boolean isNotStock(Long couponId) { diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4c00f9d..9d181d8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,7 +13,14 @@ spring: properties: hibernate: format_sql: true + open-in-view: false + redis: + host: localhost + port: 6379 logging: level: org: - hibernate.SQL: debug \ No newline at end of file + hibernate.SQL: debug +redis: + lock: + coupon: COUPON \ No newline at end of file