diff --git a/src/main/java/com/hcommerce/heecommerce/order/OrderService.java b/src/main/java/com/hcommerce/heecommerce/order/OrderService.java index e87e7f68..adf657e1 100644 --- a/src/main/java/com/hcommerce/heecommerce/order/OrderService.java +++ b/src/main/java/com/hcommerce/heecommerce/order/OrderService.java @@ -1,5 +1,7 @@ package com.hcommerce.heecommerce.order; +import com.hcommerce.heecommerce.deal.DealQueryRepository; +import com.hcommerce.heecommerce.inventory.InventoryQueryRepository; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -32,61 +34,107 @@ public void completeOrderReceipt(UUID orderUuid) { } } + /** + * placeOrder 는 주문 처리를 하는 함수로, 다음과 같은 단계로 이루어진다. + * + * 1. 유효성 검사 : orderForm의 비즈니스적 유효성 검사 + * 2. 재고량 감소 + * 3. 실제 주문량 계산 + * 4. 결제 + * 5. MySQL에 결제 및 주문 내역 저장 + * + * 만약, 재고 감소가 이루어진 후 결제 실패 등 다양한 이유로 주문 처리가 안된 경우, 다시 재고량을 증가시켜줘야 한다. + */ public void placeOrder(OrderForm orderForm) { - // 0. 유효성 검사 - // (1) userId 유효성 검사 : MySQL? or Redis(세션)? - // (2) dealProductUuid 유효성 검사 : Redis + UUID dealProductUuid = orderForm.getDealProductUuid(); - // 1. 재고량 확인 - String key = "dealProductInventory:"+orderForm.getDealProductUuid().toString(); + // 1. 유효성 검사 - int inventory = inventoryQueryRepository.get(key); // key에 해당 하는 값 없으면 Null 나옴 -> TODO : Null 처리 어떻게? Optional 활용? + // 2. 재고량 감소 + String key = "dealProductInventory:"+dealProductUuid.toString(); int orderQuantity = orderForm.getOrderQuantity(); - int realOrderQuantity = 0; + int inventoryAfterDecrease = inventoryCommandRepository.decreaseByAmount(key, orderQuantity); - if(inventory <= 0 || (inventory < orderQuantity && orderForm.getOutOfStockHandlingOption() == OutOfStockHandlingOption.ALL_CANCEL)) { - throw new OrderOverStockException(); + if(inventoryAfterDecrease == 0) { + // TODO : [품절 처리] Redis에 저장된 dealproducts 목록에서 딜 상품 상태 오픈 -> 품절로 변경 } - if (inventory < orderQuantity && orderForm.getOutOfStockHandlingOption() == OutOfStockHandlingOption.PARTIAL_ORDER) { - realOrderQuantity = inventory; // 재고량 만큼만 주문 - } else { - realOrderQuantity = orderQuantity; + // 3. 실제 주문량 계산 + int realOrderQuantity = calculateRealOrderQuantity(inventoryAfterDecrease, orderQuantity, orderForm.getOutOfStockHandlingOption()); + + // 4. 결제 : TODO : 결제 API 검토 후 추가해야할 데이터 추가 + // 1) 결제 실패하면, 재고량 다시 증가시키기 + // 2) n분의 Timeout을 정해서 시간 초과시, 재고량 다시 증가시키고 결제 종료 + boolean isSuccessPayment = false; // TODO : 임시 데이터 + + if(!isSuccessPayment) { + rollbackReducedInventory(key, realOrderQuantity); + return; } - // 2. 재고량 감소 - int currentInventory = inventoryCommandRepository.decreaseByAmount(key, realOrderQuantity); // TODO : 결제가 실패하여 재고량 다시 원상복귀해야할 때도, 분산락 걸어줘야 되나? + // 5. MySQL 주문 내역 저장 + saveOrder(); // TODO : 구체적인 건 다른 PR에서 정해지면 완성할 에정 + } - if(currentInventory == 0) { - // TODO : [품절 처리] Redis에 저장된 dealproducts 목록에서 딜 상품 상태 오픈 -> 품절로 변경 + /** + * calculateRealOrderQuantity 는 실제 주문 수량을 계산하는 함수이다. + * + * 실제 주문 수량은 감소시킨 후의 재고량, 주문량, 재고 부족 처리 옵션에 따라 달라지고, 경우의 수는 다음과 같다. + * case 1) 재고량이 0인 경우(예 : 감소시킨 후의 재고량 : -3, 주문량 : 3) : 주문 불가 + * case 2-1) 재고량은 0은 아니지만, 재고량이 주문량보다 적은 경우(예 : 감소시킨 후의 재고량 : -2, 주문량 : 3 -> 기존 재고량 : 1) + ALL_CANCEL : 주문 불가 + * case 2-2) 재고량은 0은 아니지만, 재고량이 주문량보다 적은 경우(예 : 감소시킨 후의 재고량 : -2, 주문량 : 3 -> 기존 재고량 : 1) + PARTIAL_ORDER : 주문 가능 + * case 3) 재고량이 주문량보다 많은 경우(예 : 감소시킨 후의 재고량 : 1, 주문량 : 3 -> 기존 재고 : 4) : 주문 가능 + * + * @param inventoryAfterDecrease : 감소시킨 후의 재고량 + * @param orderQuantity : 주문량 + * @param outOfStockHandlingOption : 재고 부족 처리 옵션 + * @return realOrderQuantity : 실제 주문량 + */ + private int calculateRealOrderQuantity(int inventoryAfterDecrease, int orderQuantity, OutOfStockHandlingOption outOfStockHandlingOption) { + + int inventoryBeforeDecrease = orderQuantity + inventoryAfterDecrease; + + if( + inventoryBeforeDecrease == 0 || // case 1 + inventoryBeforeDecrease < orderQuantity && outOfStockHandlingOption == OutOfStockHandlingOption.ALL_CANCEL // case 2-1 + ) { + throw new OrderOverStockException(); } - // ------ 주문 시작 - // 3. 결제 시작 -> TODO : 결제 API 검토 후 구체화하기 - // 결제 실패하면, 재고량 다시 증가시키기 - // n분의 Timeout을 정해서 시간 초과시, 재고량 다시 증가시키고 결제 종료 - boolean isSuccessPayment = false; // TODO : 임시 데이터 + int realOrderQuantity = 0; - if(!isSuccessPayment) { - inventoryCommandRepository.increaseByAmount(key, realOrderQuantity); - return; + if(inventoryBeforeDecrease < orderQuantity && outOfStockHandlingOption == OutOfStockHandlingOption.PARTIAL_ORDER) { // case 2-2 + realOrderQuantity = inventoryBeforeDecrease; // 기존 재고량 만큼만 주문 } - // 4. 결제 : TODO : 결제 API 검토 후 추가해야할 데이터 추가 - // 결제 -> TODO : 결제 API 검토 후 추가해야할 데이터 추가 - byte[] paymentUuid = convertUuidToBinary(UUID.randomUUID()); + if(inventoryAfterDecrease > 0) { // case 3 + realOrderQuantity = orderQuantity; + } + return realOrderQuantity; + } + + private void saveOrder() { + // 5-1) 결제 내역 // 총 결제 금액 // 결제 유형 // 결제 날짜 // 카드 정보 등등 - // 5. 주문 데이터 MySQL에 저장하기 + // 5-2) 주문 데이터 - // 6. 재고 : AWS SQS로 보내기 + // 5-3) 재고 + } - // ------ 주문 끝 + /** + * rollbackReducedInventory 는 임의로 감소시킨 재고량을 다시 원상복귀하기 위한 함수이다. + * 함수로 만든 이유는 다양한 원인으로 재고량을 rollback 시켜줘야 하므로, 함수로 만들어 재활용하고 싶었기 때문이다. + * @param key : 원상복귀해야 하는 딜 상품 key + * @param amount : 원상복귀해야 하는 재고량 + */ + private void rollbackReducedInventory(String key, int amount) { + return inventoryCommandRepository.increaseByAmount(key, amount); } }