종우의 삶 (전체 공개)

MSA Project - 서비스 간의 통신 본문

개발/Spring

MSA Project - 서비스 간의 통신

jonggae 2024. 7. 19. 16:33

MSA로 전환하면서 간단한 아키텍처 다이어그램을 다시 그려보았다.

 

기본적인 로직은 아래와 같다.

 

이전 포스트에서 설명했다시피 Eureka로 묶인 서비스들은 API Gatway로 들어온 요청들을 처리하게 된다.

 

그 과정에서 서로의 서비스를 불러오고, 다른 서비스의  DB를 조회해야 할 필요가 생긴다.

 

Kafka를 고려하기도 했지만, 요청을 보내고 반드시 응답이 돌아와야 다음 로직이 가능하기 때문에 Kafka와 같은 메시지 브로커는 적합하지 않다고 생각했다.

이러한 단순한 데이터 전송은 동기적으로 처리되어야 한다. 그래서 간단한 Feign Client를 사용하여 서비스 간 통신을 진행해보았다.

 

사례 1) 특정 상품을 주문에 추가하려 할 때

이러한 상황이 주어졌다고 생각해보자. 

주문을 하기 위해서는 우선 상품을 주문함에 추가를 하여야한다. 그 이후 결제를 하든 다음 과정이 진행가능하다.

 

상품을 추가하기 위해서는 상품의 정보를 알아야 한다. 그러나 상품의 정보는 Product Service에 따로 보관되어있다.

그렇기에 OrderService 에서 ProductService로의 통신이 필요하다.

 

주문 시 상품을 추가하는 로직에서 Feign Client 설정을 넣어준다.

 

@Transactional
    public OrderDto addProductToOrder(Long customerId, AddProductToOrderRequestDto requestDto) {
        Order order = orderRepository.findActiveOrderByCustomerId(customerId)
                .orElseGet(() -> createNewOrder(customerId));

        if (order.getOrderStatus() != OrderStatus.PENDING_ORDER) {
            order = createNewOrder(customerId);
        }
        ProductDto productDto = productClient.getProductOrderInfo(requestDto.getProductId());
        Long currentStock = productClient.checkStock(requestDto.getProductId());

        //재고 확인만 함
        if (currentStock < requestDto.getQuantity()) {
            throw new InsufficientStockException("재고가 부족합니다.");
        }

            OrderItem orderItem = OrderItem.builder()
                    .productId(productDto.getId())
                    .productName(productDto.getProductName())
                    .quantity(requestDto.getQuantity())
                    .price(productDto.getPrice())
                    .order(order)
                    .build();

            order.getOrderItemList().add(orderItem);
            Order savedOrder = orderRepository.save(order);

            return OrderDto.from(savedOrder);
    }

 

ProductDto productDto = productClient.getProductOrderInfo(requestDto.getProductId());
Long currentStock = productClient.checkStock(requestDto.getProductId());

 

이 부분의 productClient가 ProductService의 정보를 가져오게 된다.

간단하게 해당 Service의 엔드포인트로 요청을 보내면된다.

@FeignClient(name = "product-service")
public interface ProductClient {
    @GetMapping("/api/products/{productId}/order-info")
    @Cacheable(value = "productInfo", key = "#productId")
    ProductDto getProductOrderInfo(@PathVariable Long productId);

    @PostMapping("/api/products/{productId}/reserve-stock")
    Boolean reserveStock(@PathVariable Long productId, @RequestBody StockReservationRequestDto request);

    @GetMapping("/api/products/{productId}/stock")
    Long checkStock(@PathVariable Long productId);

    @PostMapping("/api/products/{productId}/cancel-reservation")
    void releaseStock(@PathVariable Long productId, @RequestBody StockReservationRequestDto request);

    @PostMapping("/api/products/{productId}/confirm")
    void confirmStockReservation(@PathVariable("productId") Long productId, @RequestParam("quantity") Long quantity);

    @PostMapping("/api/products/{productId}/cancel")
    void cancelStockReservation(@PathVariable("productId") Long productId, @RequestParam("quantity") Long quantity);
}

 

Product Service 내의 로직들은 이에 맞게 구현하거나, 이미 만들어져 있는 메서드들을 사용하면 되는데, 어떤 정보를 받아와야 하는지 DTO를 적절하게 생성해야 한다. 

 

public class ProductDto {
    private Long id;
    private String productDescription;
    private String productName;
    private Long price;
    private Long stock;
}

 

Product Service 에서 받아올 데이터들을 이렇게 설정하면, 

OrderItem이라는 주문 항목의 객체를 생성할 수 있다. (처음 메서드 코드 참고)

 

생각보다 서비스 간 통신이 많이 발생하지는 않는데, 특정한 로직에서만 외부의 서비스가 필요한 것이었다. 

 

결제도 비슷하게 진행된다.

@FeignClient(name = "payment-service")
public interface PaymentClient {
    @PostMapping("/api/payments/process")
    PaymentResult processPayment(@RequestBody PaymentRequestDto requestDto);
}

 

processPayment는 간단히 결제 성공 / 실패 여부를 반환해 준다. 

현재 로직에서 결제가 실제로 진행되는 부분은 일반적인 주문에서 "주문 확인" 과정과

예약 구매 진행시 상품을 고르고 곧바로 주문을 시도할 때이다.

 

이 상황에서도 Payment Service를 호출하여 로직의 결과를 반환한다. - PaymentResult

 

그렇다면 여기서 살펴보아야할 것은 일반 주문이 아닌 예약 구매시 1만 건의 데이터, 즉 동시다발적인 요청을 어떻게 처리할 수 있는가에 대한 점이다. 

 

다음 포스트에서는 예약 구매시 락 획득이 어떻게 이루어지는지 살펴보자. 

Comments