Skip to content

Commit

Permalink
feat: 주문서 만료 시 COMPLETED 여부 확인 & 결제 완료 시 Redis 주문서 정보 삭제 (#229)
Browse files Browse the repository at this point in the history
* refactor: 이벤트 리스너 클래스 삭제 및 이벤트 로직을 Service로 이관

* feat: 주문서 만료 이벤트 발생 시 상태가 COMPLETED일 경우 취소하도록 수정

* feat: 결제 완료 시 비동기 이벤트를 발행해 Redis Receipt 삭제

* fix: COMPLETED가 아닌 EXPIRED 여부를 확인하고 있던 로직 수정

* refactor: 클릭수, 노출수 증가 시 락 점유 및 대기 시간을 3초에서 1초로 변경

* feat: 진행중인 주문서의 상태만 변경할 수 있도록 수정, 주문서 이벤트 리스너 클래스 생성
  • Loading branch information
hwihwi523 authored Sep 18, 2023
1 parent e4b913e commit df226e7
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
public class LiquorCtrRedisRepository {
// TODO: ~Repository vs ~Service
private static final String LIQUOR_CTR_KEY = "LIQUOR_CTR";
private static final long LOCK_WAIT_TIME = 3L;
private static final long LOCK_LEASE_TIME = 3L;
private static final long LOCK_WAIT_TIME = 1L;
private static final long LOCK_LEASE_TIME = 1L;
private static final long LIQUOR_CTR_TTL = 5L;

private final LiquorCtrRepository liquorCtrRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.woowacamp.soolsool.core.receipt.domain.Receipt;
import com.woowacamp.soolsool.core.receipt.domain.ReceiptItem;
import com.woowacamp.soolsool.core.receipt.domain.vo.ReceiptStatusType;
import com.woowacamp.soolsool.core.receipt.event.ReceiptRemoveEvent;
import com.woowacamp.soolsool.core.receipt.service.ReceiptService;
import com.woowacamp.soolsool.global.exception.SoolSoolException;
import com.woowacamp.soolsool.global.infra.LockType;
Expand All @@ -23,6 +24,7 @@
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -42,6 +44,8 @@ public class PayService {

private final PayClient payClient;

private final ApplicationEventPublisher publisher;

private final RedissonClient redissonClient;

@Transactional
Expand All @@ -58,6 +62,7 @@ public Order approve(final Long memberId, final Long receiptId, final String pgT

final List<RLock> locks = new ArrayList<>();
locks.add(getMemberLock(memberId));
locks.add(getReceiptLock(receiptId));
locks.addAll(getLiquorLocks(receipt.getReceiptItems()));

final RLock multiLock = redissonClient.getMultiLock(
Expand All @@ -80,6 +85,8 @@ public Order approve(final Long memberId, final Long receiptId, final String pgT
orderService.addPaymentInfo(
payClient.payApprove(receipt, pgToken).toEntity(order.getId()));

publisher.publishEvent(new ReceiptRemoveEvent(receiptId));

return order;
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
Expand All @@ -94,6 +101,10 @@ private RLock getMemberLock(final Long memberId) {
return redissonClient.getLock(LockType.MEMBER.getPrefix() + memberId);
}

private RLock getReceiptLock(final Long receiptId) {
return redissonClient.getLock(LockType.RECEIPT.getPrefix() + receiptId);
}

private List<RLock> getLiquorLocks(final List<ReceiptItem> receiptItems) {
return receiptItems.stream()
.map(ReceiptItem::getLiquorId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ public enum ReceiptErrorCode implements ErrorCode {
NOT_EQUALS_MEMBER(BAD_REQUEST.value(), "R116", "다른 사용자의 주문서를 가지고 있습니다."),

NOT_RECEIPT_TYPE_FOUND(NOT_FOUND.value(), "R117", "주문서 상태가 존재하지 않습니다."),
UNMODIFIABLE_STATUS(BAD_REQUEST.value(), "R122", "진행중인 주문서만 변경할 수 있습니다."),

MEMBER_NO_INFORMATION(400, "R118", "회원 정보를 찾을 수 없습니다."),

ACCESS_DENIED_RECEIPT(403, "R119", "본인의 주문서 내역만 조회할 수 있습니다."),
NOT_FOUND_RECEIPT(404, "P002", "회원의 주문 내역을 찾을 수 없습니다."),
NOT_FOUND_RECEIPT(404, "R120", "회원의 주문 내역을 찾을 수 없습니다."),

INTERRUPTED_THREAD(500, "R121", "예상치 못한 예외가 발생했습니다."),
;

private final int status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.woowacamp.soolsool.core.receipt.domain.converter.ReceiptItemQuantityConverter;
import com.woowacamp.soolsool.core.receipt.domain.vo.ReceiptItemPrice;
import com.woowacamp.soolsool.core.receipt.domain.vo.ReceiptItemQuantity;
import com.woowacamp.soolsool.core.receipt.domain.vo.ReceiptStatusType;
import com.woowacamp.soolsool.global.common.BaseEntity;
import java.math.BigInteger;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -96,6 +97,10 @@ public void updateStatus(final ReceiptStatus receiptStatus) {
this.receiptStatus = receiptStatus;
}

public boolean isNotInProgress() {
return !receiptStatus.getType().equals(ReceiptStatusType.INPROGRESS);
}

public BigInteger getOriginalTotalPrice() {
return originalTotalPrice.getPrice();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.woowacamp.soolsool.core.receipt.event;

import lombok.Getter;

@Getter
public class ReceiptRemoveEvent {

private final Long receiptId;

public ReceiptRemoveEvent(final Long receiptId) {
this.receiptId = receiptId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Slf4j
@RequiredArgsConstructor
public class ReceiptExpiredEventListener {
public class ReceiptEventListener {

private final ReceiptService receiptService;

@Async
@EventListener
public void expiredListener(final ReceiptExpiredEvent event) {
public void expireReceipt(final ReceiptExpiredEvent event) {
receiptService.modifyReceiptStatus(
event.getMemberId(), event.getReceiptId(), ReceiptStatusType.EXPIRED
event.getMemberId(),
event.getReceiptId(),
ReceiptStatusType.EXPIRED
);

log.info("Member {}'s Receipt {} Expired", event.getMemberId(), event.getReceiptId());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package com.woowacamp.soolsool.core.receipt.repository.redisson;

import com.woowacamp.soolsool.core.receipt.event.ReceiptExpiredEvent;
import com.woowacamp.soolsool.core.receipt.event.ReceiptRemoveEvent;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.redisson.api.map.event.EntryExpiredListener;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Slf4j
@Component
public class ReceiptRedisRepository {

private static final String RECEIPT_EXPIRED_KEY = "RECEIPT_EXPIRED";

private final RedissonClient redissonClient;
private final RMapCache<Long, Long> receiptCache;

public ReceiptRedisRepository(
final RedissonClient redissonClient,
Expand All @@ -26,14 +30,18 @@ public ReceiptRedisRepository(
-> publisher.publishEvent(new ReceiptExpiredEvent(event.getKey(), event.getValue()))
);

this.redissonClient = redissonClient;
this.receiptCache = redissonClient.getMapCache(RECEIPT_EXPIRED_KEY);
}

public void addExpiredEvent(final Long receiptId, final Long memberId, final long minutes) {
final RMapCache<Long, Long> receiptCache = redissonClient.getMapCache(RECEIPT_EXPIRED_KEY);

receiptCache.put(receiptId, memberId, minutes, TimeUnit.MINUTES);

log.info("Complete to save Member {}'s Receipt {} to Redis", memberId, receiptCache);
}

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void removeReceipt(final ReceiptRemoveEvent event) {
receiptCache.remove(event.getReceiptId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
import com.woowacamp.soolsool.core.receipt.repository.ReceiptStatusCache;
import com.woowacamp.soolsool.core.receipt.repository.redisson.ReceiptRedisRepository;
import com.woowacamp.soolsool.global.exception.SoolSoolException;
import com.woowacamp.soolsool.global.infra.LockType;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -28,6 +32,8 @@
public class ReceiptService {

private static final int RECEIPT_EXPIRED_MINUTES = 5;
private static final long LOCK_WAIT_TIME = 3L;
private static final long LOCK_LEASE_TIME = 3L;

private final ReceiptMapper receiptMapper;
private final ReceiptRepository receiptRepository;
Expand All @@ -37,6 +43,8 @@ public class ReceiptService {

private final ReceiptRedisRepository receiptRedisRepository;

private final RedissonClient redissonClient;

@Transactional
public Long addReceipt(final Long memberId) {
final Member member = memberRepository.findById(memberId)
Expand All @@ -54,8 +62,7 @@ public Long addReceipt(final Long memberId) {

@Transactional(readOnly = true)
public ReceiptDetailResponse findReceipt(final Long memberId, final Long receiptId) {
final Receipt receipt = receiptRepository.findById(receiptId)
.orElseThrow(() -> new SoolSoolException(NOT_RECEIPT_FOUND));
final Receipt receipt = getReceipt(receiptId);

if (!Objects.equals(receipt.getMemberId(), memberId)) {
throw new SoolSoolException(NOT_EQUALS_MEMBER);
Expand All @@ -70,16 +77,49 @@ public void modifyReceiptStatus(
final Long receiptId,
final ReceiptStatusType receiptStatusType
) {
final Receipt receipt = receiptRepository.findById(receiptId)
final RLock receiptLock = getReceiptLock(receiptId);

try {
receiptLock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS);

final Receipt receipt = getReceipt(receiptId);

if (!Objects.equals(receipt.getMemberId(), memberId)) {
throw new SoolSoolException(NOT_EQUALS_MEMBER);
}

if (receipt.isNotInProgress()) {
throw new SoolSoolException(ReceiptErrorCode.UNMODIFIABLE_STATUS);
}

receipt.updateStatus(
getReceiptStatus(receiptStatusType)
);
} catch (final InterruptedException e) {
throw new SoolSoolException(ReceiptErrorCode.INTERRUPTED_THREAD);
} finally {
unlock(receiptLock);
}
}

private Receipt getReceipt(final Long receiptId) {
return receiptRepository.findById(receiptId)
.orElseThrow(() -> new SoolSoolException(NOT_RECEIPT_FOUND));
final ReceiptStatus receiptStatus = receiptStatusCache.findByType(receiptStatusType)
}

private ReceiptStatus getReceiptStatus(final ReceiptStatusType receiptStatusType) {
return receiptStatusCache.findByType(receiptStatusType)
.orElseThrow(() -> new SoolSoolException(ReceiptErrorCode.NOT_RECEIPT_TYPE_FOUND));
}

if (!Objects.equals(receipt.getMemberId(), memberId)) {
throw new SoolSoolException(NOT_EQUALS_MEMBER);
}
private RLock getReceiptLock(final Long receiptId) {
return redissonClient.getLock(LockType.RECEIPT.getPrefix() + receiptId);
}

receipt.updateStatus(receiptStatus);
private void unlock(final RLock lock) {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public enum LockType {
LIQUOR_CTR("LIQUOR_CTR:"),
LIQUOR_STOCK("LIQUOR_STOCK:"),
ORDER("ORDER:"),
RECEIPT("RECEIPT:"),
;

private final String prefix;
Expand Down

0 comments on commit df226e7

Please sign in to comment.