[PetLink] Order Api [ 2 ]

DeadWhale·2023년 8월 4일

image


이제 실제로 재고를 감소하면서 주문 정보를 만들어야 합니다.
크게 3가지 Step으로 작성될것 같습니다.

  • Step 1 : 리워드들 재고 감소
  • Step 2 : 결제 번호 채번
  • Step 3 : 결제 번호 생성

Step 1 : 리워드들 재고 감소

// 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;
    }
}

크게 두가지 서비스가 존재합니다.

  1. 재고를 실제로 감소하는 메소드
  2. 동시성 제어를 위한 락을 받은 메소드

동시성 제어에 관한 글을 다른 글을 참고 바랍니다. → 100명이 제품을 동시에 구매한다면?


Step 2 : 주문 번호 채번

이 부분을 작성하면서 고려할 점은 3가지였습니다.

  1. 유니크 ID이여야 한다.(중복되지 않아야한다)
  2. 주문번호에서 어느 정도 정보가 추출되어야한다 (정렬조건으로 활용할 수 있도록)
  3. DB를 갔다오지 않아야 한다.

DB에 접근하지 않으면서 주문번호를 생성해야한다.

여러 방법을 통해 유니크하면서 효율적인 주문번호를 만들 수 있었습니다.


@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();
    }
}

0개의 댓글