이제 실제로 재고를 감소하면서 주문 정보를 만들어야 합니다.
크게 3가지 Step으로 작성될것 같습니다.
// step 1 : 리워드들 재고 감소
itemFacadeService.decrease(orderRequest.getFundingItems());
이 서비스에서 바로 재고를 감소 할 수 있겟지만, 좋은 코드처럼 느껴지진 않습니다.
이 서비스는 주문만 하고 재고 감소는 itemFacadeService에게 요청하겠습니다.
@Slf4j
@Component
@RequiredArgsConstructor
public class ItemFacadeService {
private final RedissonClient redissonClient;
private final ItemRepository itemRepository;
// 주어진 펀딩 아이템 목록의 수량을 감소시킵니다.
public void decrease(List<FundingItemDto> fundingItems) throws ItemException, OrdersException, InterruptedException {
for (FundingItemDto item : fundingItems) {
FundingItem fundingItem = itemRepository.findById(item.getFundingItemId()).orElseThrow(() -> new ItemException(ITEM_NOT_FOUND));
RLock lock = tryLockItem(item.getFundingItemId(), fundingItem.getTitle());
try {
fundingItem.decrease(item.getQuantity());
} finally {
lock.unlock();
}
}
}
// 주어진 아이디에 해당하는 아이템에 락을 시도하고, 락 객체를 반환합니다.
private RLock tryLockItem(Long itemId, String itemTitle) throws OrdersException, InterruptedException {
RLock lock = redissonClient.getLock(itemId.toString());
boolean available = lock.tryLock(20, 2, TimeUnit.SECONDS);
if (!available) {
log.info("lock is not available : 락 점유 실패.({}) : {}",
itemTitle,
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
throw new OrdersException(CANNOT_BUY_NOW); //현재 구매할 수 없습니다(락 점유 실패)
}
return lock;
}
}
크게 두가지 서비스가 존재합니다.
동시성 제어에 관한 글을 다른 글을 참고 바랍니다. → 100명이 제품을 동시에 구매한다면?
이 부분을 작성하면서 고려할 점은 3가지였습니다.
여러 방법을 통해 유니크하면서 효율적인 주문번호를 만들 수 있었습니다.
@Service
@RequiredArgsConstructor
public class MemberOrderService {
private final OrderRepository orderRepository;
private final FundingRepository fundingRepository;
private final MemberRepository memberRepository;
private final ItemFacadeService itemFacadeService;
private final OrderNumbersGenerator generator; // 결제 번호 생성기
//주문을 생성하는 기능. ( 비회원 구매 )
public OrderResponseDto createOrderByMember(OrderRequest orderRequest, Long memberId) throws InterruptedException {
// step 1 : 리워드 재고 감소
itemFacadeService.decrease(orderRequest.getFundingItems());
// step 2 , 3 : 결제 번호 채번 결제 생성
Long fundingId = orderRequest.getFundingId();
Funding funding = fundingRepository.findById(fundingId).orElseThrow(() -> new FundingException(FUNDING_NOT_FOUND));
Member member = memberRepository.findById(memberId).orElseThrow(() -> new RuntimeException("회원이 존재하지 않습니다."));
Orders orders = orderRepository.saveAndFlush(getBuild(orderRequest, funding, member));
return OrderResponseDto.builder()
.orderNumber(orders.getPaymentNumber())
.orderId(orders.getId())
.fundingId(fundingId)
.recipientInfo(OrderResponseDto.RecipientInfo.of(orders.getRecipient(), orders.getAddress(), orders.getMobilePhone(), orders.getSubPhone()))
.orderedRewards(orders.getFundingItemOrders().stream().map(fio -> fio.getFundingItem().getTitle()).toList())
.isAmountOpen(orders.getPriceOpen())
.isNameOpen(orders.getNameOpen())
.build();
}
private Orders getBuild(OrderRequest orderRequest, Funding funding, Member member) {
return Orders.builder()
.funding(funding)
.member(member)
.paymentNumber("M-" + generator.generateOrderNumber()) // step 2 : 결제 번호 생성
.payMethod(orderRequest.getPayMethod())
.nameOpen(orderRequest.isNameOpen())
.priceOpen(orderRequest.isAmountOpen())
.recipient(orderRequest.getRecipient())
.address(Address.of(orderRequest.getZipCode(), orderRequest.getAddress(), orderRequest.getDetailAddress()))
.mobilePhone(orderRequest.getPhone())
.subPhone(orderRequest.getSubPhone())
.build();
}
}