๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ–ฅ๏ธ TurtleMartํ”„๋กœ์ ํŠธ/โœ๏ธ TIL

[TIL] ํ”„๋กœ์ ํŠธ 24์ผ์ฐจ.. ์ „์ฒด์ ์ธ ๋กœ์ง ๋งˆ๋ฌด๋ฆฌํ•˜๊ธฐ!!

by carrot0911 2025. 6. 11.

๐ŸŒž ์˜ค๋Š˜์€ ์–ด๋–ค ํ•˜๋ฃจ์˜€์ง€..

์šฐ์„  ํŒ€์›์˜ ํ”ผ๋“œ๋ฐฑ๋ถ€ํ„ฐ ์ฝ”๋“œ์— ๋ฐ˜์˜ํ–ˆ๋‹ค.
Kafka๋กœ ์žฌ๊ณ  ๋ณต์› ์š”์ฒญ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋Š” ๋ถ€๋ถ„์ด ์ค‘๋ณต๋˜์–ด์„œ ๋ฉ”์„œ๋“œ๋กœ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์ด ๋‚˜์•„ ๋ณด์ธ๋‹ค๋Š” ์˜๊ฒฌ์— ์ˆ˜๊ธํ•˜๊ณ  ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”์ถœํ–ˆ๋‹ค.

private void sendInventoryRestoreMessage(CreateDeliveryRequest request) {
    String payload = JsonHelper.toJson(request);
    OperationWrapperDto wrapper = OperationWrapperDto.from(OperationType.DELIVERY_FAIL_INVENTORY_RESTORE, payload);
    String wrappedMessage = JsonHelper.toJson(wrapper);

    kafkaTemplate.send(productTopic, request.orderId().toString(), wrappedMessage);

    log.info("\uD83D\uDCE4 Kafka ์žฌ๊ณ  ๋ณต์› ๋ฉ”์‹œ์ง€ ์ „์†ก: {}", wrappedMessage);
}
@Transactional
public CreateDeliveryResponse createDelivery(CreateDeliveryRequest request) {
    if (!orderRepository.existsById(request.orderId())) {
        sendInventoryRestoreMessage(request);

        slackNotifier.sendDeliveryCreateFailureAlert(
            request.orderId(),
            "ORDER_NOT_FOUND",
            "ID๊ฐ€ " + request.orderId() + "์ธ ์ฃผ๋ฌธ์ด ์กด์žฌํ•˜์ง€ ์•Š์•„ ๋ฐฐ์†ก ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");

        throw new NotFoundException(ErrorCode.ORDER_NOT_FOUND);
    }

    // ํ•ด๋‹น ์ฃผ๋ฌธ์— ๋Œ€ํ•œ ๋ฐฐ์†ก์ด ์กด์žฌํ•  ๊ฒฝ์šฐ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
    if (deliveryRepository.existsByOrderId(request.orderId())) {
        sendInventoryRestoreMessage(request);

        slackNotifier.sendDeliveryCreateFailureAlert(
            request.orderId(),
            "DELIVERY_ALREADY_EXISTS",
            "ID๊ฐ€ " + request.orderId() + "์ธ ์ฃผ๋ฌธ์— ๋Œ€ํ•œ ๋ฐฐ์†ก์ด ์ด๋ฏธ ์กด์žฌํ•˜์—ฌ ๋ฐฐ์†ก ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");

        throw new ConflictException(ErrorCode.DELIVERY_ALREADY_EXISTS);
    }

    if (!sellerRepository.existsById(request.sellerId())) {
        sendInventoryRestoreMessage(request);

        slackNotifier.sendDeliveryCreateFailureAlert(
            request.orderId(),
            "SELLER_NOT_FOUND",
            "ID๊ฐ€ " + request.sellerId() + "์ธ ํŒ๋งค์ž๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„ ๋ฐฐ์†ก ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");

        throw new NotFoundException(ErrorCode.SELLER_NOT_FOUND);
    }

    if (!senderRepository.existsById(request.senderId())) {
        sendInventoryRestoreMessage(request);

        slackNotifier.sendDeliveryCreateFailureAlert(
            request.orderId(),
            "SENDER_NOT_FOUND",
            "ID๊ฐ€ " + request.senderId() + "์ธ ์ถœ๊ณ ์ง€(๋ฌผ๋ฅ˜์„ผํ„ฐ)๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„ ๋ฐฐ์†ก ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");

        throw new NotFoundException(ErrorCode.SENDER_NOT_FOUND);
    }

    if (!addressRepository.existsById(request.addressId())) {
        sendInventoryRestoreMessage(request);

        slackNotifier.sendDeliveryCreateFailureAlert(
            request.orderId(),
            "ADDRESS_NOT_FOUND",
            "ID๊ฐ€ " + request.addressId() + "์ธ ์ฃผ์†Œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์•„ ๋ฐฐ์†ก ์ƒ์„ฑ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.");

        throw new NotFoundException(ErrorCode.ADDRESS_NOT_FOUND);
    }

    Order order = orderRepository.getReferenceById(request.orderId());
    Seller seller = sellerRepository.getReferenceById(request.sellerId());
    Sender sender = senderRepository.getReferenceById(request.senderId());

    Address address = addressRepository.findById(request.addressId())
        .orElseThrow(() -> new NotFoundException(ErrorCode.ADDRESS_NOT_FOUND));

    Delivery delivery = Delivery.of(order, seller, sender, address, request.deliveryRequest());

    deliveryRepository.save(delivery);

    // ์Šฌ๋ž™ ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ „์†ก
    slackNotifier.sendDeliveryCreateAlert(
        request.orderId(),
        delivery.getOrder().getMember().getName(),
        delivery.getOrder().getMember().getPhoneNumber(),
        delivery.getReceiverName(),
        delivery.getReceiverPhone(),
        delivery.getReceiverAddress(),
        delivery.getReceiverDetailAddress());

    return CreateDeliveryResponse.from(delivery);
}

๊ทธ๋‹ค์Œ ์นด์นด์˜คํ†ก ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ „์†ก์—์„œ ๋ฐฐ์†ก ์ค‘๊ณผ ๋ฐฐ์†ก ์™„๋ฃŒ์˜ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์นด์นด์˜คํ†ก ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€๊ฐ€ ์ „์†ก๋  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค.

@Transactional
public UpdateDeliveryResponse updateTrackingNumber(Long deliveryId, UpdateDeliveryRequest request) {
    Delivery delivery = getDelivery(deliveryId);

    delivery.updateTrackingNumber(request.trackingNumber());

    // ์นด์นด์˜คํ†ก ์ถœ๊ณ  ์™„๋ฃŒ ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ „์†ก
    UserNotification userNotification = UserNotification.from(delivery);
    kakaoMessageService.sendShippedMessage(userNotification);

    return UpdateDeliveryResponse.from(delivery);
}

๋ฐฐ์†ก ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์นด์นด์˜คํ†ก ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€๊ฐ€ ์ „์†ก๋  ์ˆ˜ ์žˆ๋„๋ก ํ–ˆ๋‹ค!

๊ทธ ํ›„์— ๊ฒฐ์ œ์—์„œ ๋„˜์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•ด์„œ ์žฌ๊ณ  ๊ฐ์†Œ๋ฅผ ์ง„ํ–‰ํ•˜๊ณ  ๋ฐฐ์†ก ์ƒ์„ฑ๊นŒ์ง€ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋„๋ก ๋กœ์ง์„ ์™„์„ฑํ–ˆ๋‹ค.

CreateDeliveryRequest request = (CreateDeliveryRequest) session.getAttribute("delivery");
String payload = JsonHelper.toJson(request);
String key = "";
String value = JsonHelper.toJson(OperationWrapperDto.from(OperationType.ORDER_PAYMENT_INVENTORY_DECREASE, payload));

stringKafkaTemplate.send("order_make_topic", key, value);

return ResponseEntity.status(code).body(jsonObject);
// ์žฌ๊ณ  ๊ด€๋ จ Kafka Listener
@KafkaListener(topics = "${kafka.topic.product}", groupId = "${spring.kafka.consumer.product-combination.group-id}")
public void listenInventory(@Header(KafkaHeaders.RECEIVED_KEY) String key, String value) {
    try {
        OperationWrapperDto wrapperDto = JsonHelper.fromJson(value, OperationWrapperDto.class);
        OperationType type = wrapperDto.operationType();
        String payload = wrapperDto.payload();

        switch (type) {
            case ORDER_PAYMENT_INVENTORY_DECREASE -> routeInventoryDecreaseMessage(key, payload);
            case DELIVERY_FAIL_INVENTORY_RESTORE -> routeInventoryRestoreMessage(key, payload);
            default -> log.error("โŒ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ฉ”์‹œ์ง€ ํƒ€์ž… ์ˆ˜์‹ : {}", type);
        }
    } catch (ConflictException e) {
        log.warn("โš ๏ธ ์žฌ๊ณ  ๋ถ€์กฑ์œผ๋กœ ์žฌ๊ณ  ๊ฐ์†Œ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์‹คํŒจ: {}", e.getMessage());
    } catch (NotFoundException e) {
        log.warn("โš ๏ธ ํ•„์ˆ˜ ๋ฐ์ดํ„ฐ ๋ˆ„๋ฝ์œผ๋กœ ์žฌ๊ณ  ๊ฐ์†Œ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์‹คํŒจ ({}): {}", e.getErrorCode(), e.getMessage());
    } catch (NumberFormatException e) {
        log.warn("โš ๏ธ ์ž˜๋ชป๋œ orderId ํ˜•์‹์ž…๋‹ˆ๋‹ค. key: {}, message: {}", key, e.getMessage());
    } catch (Exception e) {
        log.error("โŒ ์˜ˆ๊ธฐ์น˜ ๋ชปํ•œ ์˜ค๋ฅ˜๋กœ ์žฌ๊ณ  ๊ฐ์†Œ ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ ์‹คํŒจ: {}", e.getMessage());
    }
}
private void routeInventoryDecreaseMessage(String key, String value) {
    // ๊ฒฐ์ œ ํŒŒํŠธ์—์„œ ์ „๋‹ฌ๋˜๋Š” value ํ™•์ธ ํ›„ DTO ์ •์˜
    log.info("๐Ÿ“ฅ Kafka ์žฌ๊ณ  ๊ฐ์†Œ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ : key: {}, value: {}", key, value);

    CreateDeliveryRequest request = JsonHelper.fromJson(value, CreateDeliveryRequest.class);

    // ์žฌ๊ณ  ๊ฐ์†Œ ๋กœ์ง ์ง„ํ–‰
     productOptionCombinationService.decreaseProductOptionCombinationInventory(Long.valueOf(key));
     log.info("๐Ÿ‘‰ ์žฌ๊ณ  ๊ฐ์†Œ ์„ฑ๊ณต! ๋ชจ๋“  ์ƒํ’ˆ์˜ ์žฌ๊ณ  ์ฐจ๊ฐ์ด ์ •์ƒ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");

    // ๋ฐฐ์†ก ์ƒ์„ฑ ์š”์ฒญ ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰
    String payload = JsonHelper.toJson(request);
    String message = JsonHelper.toJson(OperationWrapperDto.from(OperationType.DELIVERY_CREATE, payload));

    kafkaTemplate.send(deliveryTopic, key, message);

    log.info("\uD83D\uDCE4 Kafka ๋ฐฐ์†ก ์ƒ์„ฑ ๋ฉ”์‹œ์ง€ ์ „์†ก: {}", request);
}

private void routeInventoryRestoreMessage(String key, String value) {
    log.info("๐Ÿ“ฅ Kafka ์žฌ๊ณ  ๋ณต์› ๋ฉ”์‹œ์ง€ ์ˆ˜์‹ : key={}, value={}", key, value);

    // ์žฌ๊ณ  ๋ณต์› ๋กœ์ง ์ง„ํ–‰
    Long orderId = Long.valueOf(key);
    productOptionCombinationService.restoreProductOptionCombinationInventory(orderId);
}

์ด๋ ‡๊ฒŒ ๊ฒฐ์ œ ์™„๋ฃŒ ํ›„ ๋„˜์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ๋กœ ์žฌ๊ณ  ๊ฐ์†Œ ์ง„ํ–‰ ํ›„ ๋ฐฐ์†ก ์ƒ์„ฑ๊นŒ์ง€ ๋งˆ๋ฌด๋ฆฌ๋  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ–ˆ๋‹ค!

๋งˆ์ง€๋ง‰์œผ๋กœ ๊ฐ€๊ฒฉ ๋ณ€๋™์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„์ง€ ๊ณ ๋ฏผ์„ ํ•˜๋‹ค๊ฐ€ ํ•˜๋ฃจ๊ฐ€ ๋๋‚˜๋ฒ„๋ ธ๋‹ค..

 

๐Ÿ—“๏ธ ๋‚ด์ผ์€ ๋ญ ํ•˜์ง€?!

โœ”๏ธ ๊ฐ€๊ฒฉ ๋ณ€๋™ ๋กœ์ง ๋งˆ๋ฌด๋ฆฌํ•˜๊ธฐ
โœ”๏ธ API ๋ช…์„ธ์„œ ์ˆ˜์ •ํ•˜๊ธฐ
โœ”๏ธ TIL ์ž‘์„ฑํ•˜๊ธฐ