NDM

상품 조회 api와 주문 api 설계하기 본문

[프로젝트] 타임딜 API 서버

상품 조회 api와 주문 api 설계하기

ndm.jr 2023. 6. 3. 12:37

현재 진행중인 토이프로젝트는

"타임딜" 이라는 단시간에 많은 트래픽이 몰리는 프로모션 상품 구매 서비스 api 서버입니다.

 

현재 제 프로젝트의 주요한 api는 다음 2개입니다.

  1. 상품 조회 api
  2. 주문 api

 

비즈니스 로직이 어떻게 진행되는지 알기 위해, 시퀀스 다이어그램을 작성해 보았습니다.


# 상품 조회

 

상품 조회의 요구사항은 다음과 같습니다.

 

  1. 일반 상품과 이벤트 프로모션 상품이 존재합니다.
  2. 상품 조회는 페이징 처리가 필수적입니다.
  3. 이벤트 프로모션 상품의 경우, 이미 이벤트가 진행중인 시점이라면 상품 수정 및 이벤트 해제가 불가능합니다.
  4. 이벤트 상품과 일반 상품 모두 조회 endPoint가 같으며, 쿼리스트링의 파라미터로 구분하였습니다.

 

다음은 시퀀스 다이어그램입니다.

 

 

기본적인 구상은 다음과 같습니다.

 

  1. 일반 상품의 경우, 기본적으로 RDB를 이용해 조회합니다.
  2. 이벤트 상품의 경우, Redis를 이용한 캐싱을 사용했습니다. 첫 요청의 경우 RDB에서 조회해 Redis에 캐싱하고, 이후 요청부터는 RDB가 아닌 Redis에 접근해서 데이터를 가져옵니다.

이벤트 상품의 경우 상품 자체의 변경이 적은편이어서 캐싱 대상에 적합하다고 생각하였고,

Spring의 @Cacheable의 SpEL을 이용해 특정 이벤트의 상품만이 아닌 Event별로, Page별로 모두 캐싱이 적용될 수 있게 구성했습니다.

EhCache나 CaffeineCache같은 로컬캐시로 캐싱을 적용할 수도 있었으나, 이후 Scale-Out을 고려한다면 글로벌 캐시를 적용하는 것이 더 낫다고 판단하였습니다.

 

그러나 분명 개선점도 필요해 보입니다.

 

일반 상품의 경우 모두 RDB에서 조회해옵니다. 프로젝트의 규모가 커질수록 가져오는 데이터가 많아질 것입니다.
일반 상품 조회 시 초기 데이터를 불러올 때 적절한 카테고리 구성이나, 필터링을 통해 데이터를 일부만 가져오는 작업이 필요할 것으로 생각됩니다. 또한, 이러한 작업도 빠르게 처리하기 위해 DB에 Index설정이 필요할 듯 합니다.

 

** 위 방법으로 하면 문제가 생길 수 있음을 깨닫고 다음과 같이 변경하였습니다. **

 

https://ndm-tech.tistory.com/87

 

상품조회 api 다시X3 시스템 디자인하기

기존의 제 이벤트 상품 조회는 다음과 같이 이루어집니다. 첫 번째로 DB를 조회해서 Cache DB에 적재하고, 그 이후부터 Cache DB에서 데이터를 가져오는 방식입니다. 하지만 다음과 같은 문제점이 있

ndm-tech.tistory.com


# 주문

 

 

주문의 요구사항은 다음과 같습니다.

  1. 한 주문에 여러개의 주문상품이 담길 수 있습니다.
  2. 한 주문에 일반 상품과 이벤트 상품 모두 담길 수 있습니다.
  3. 만약 주문에 담긴 상품 중 하나라도 재고가 부족하다면, 주문에 담긴 모든 상품이 취소되어야 합니다.
  4. 만약 "재고 부족" 이 아닌 다른 문제로 주문이 실패하였다면, 재고를 원복하지 않습니다.

 

기존에도 주문 로직을 작성해본 적이 있지만, 특별히 더 많이 고민했던 프로젝트입니다. 고민과 결과는 다음과 같습니다.

 

  1. 장바구니 도메인을 설정하지 않았습니다.
    • 장바구니는 일시적인 셩격을 띄기 떄문에 완벽한 정합성을 위해 굳이 RDB에 저장할 필요는 없다고 생각했습니다. 로컬스토리지같은 클라이언트 측에서 관리하는 것이 맞다고 생각하여 따로 도메인을 구성하지 않았습니다.
  2. 주문 로직의 마지막에 save 하는것이 아닌, 우선 빈 주문지를 만들고 이후 로직을 진행했습니다.
    • 그동안은 비즈니스 로직이 진행되며 주문 데이터를 만들고 마지막에 저장했었지만, 여러 문제가 있다고 판단했습니다. 예외가 발생할 시에는 모두 롤백되기에 이력을 전혀 관리할 수 없으며, 
  3. "재고 부족 예외"가 아닌 다른 예외라면, 재고를 원복하지 않았습니다.
    • "재고 부족"으로 인해 예외가 발생하였다면 <감소시켰던 재고 원복 - 주문 실패처리>로 로직을 진행해야 합니다. 하지만 만약 예를들어 외부 PG모듈 문제로 주문이 실패하여 예외가 발생했다면 그것을 주문 실패로 보아야 하는 것인가? 라는 생각이 들었습니다. 주문에는 성공했지만 결제 측면에서 예외가 발생한 것이기에 결제 측에 Retry로직을 통해 다시 결제를 요청하고, 만약 끝까지 결제가 실패하였다면 이후 배치작업을 통해 한번에 재고를 원복시키는 것이 맞다고 판단하였습니다. 또한 이러한 생각까지 오면서 외부 api를 사용하는 경우 서킷브레이커 패턴을 사용할 수 있다고 생각하였고, 마이크로서비스로 분리될 수 있는 지점이라고 생각하였습니다.
  4. Redis와 RDB의 싱크를 위해 주문 이력 테이블을 따로 구성했습니다.
    • Redis 자체로도 RDB나 AOF같은 기능을 지원하지만 RDB에 이력을 저장하여 싱크를 맞추도록 하였습니다.
      Redis에 문제가 있는 경우 바로 RDB에서 조회한 다음 다시 Redis에 저장하고 로직을 실행하도록 했습니다.
  5. Redis Client를 Lettuce(Default)에서 Redisson으로 변경하였습니다.
    • 1인당 하나만 발급받는 선착순 쿠폰 이벤트같은 경우, Zset으로도 재고를 손쉽게 해결할 수 있습니다. 하지만 제 프로젝트에서는 하나의 주문에 여러 상품들이 담기고, 그 주문상품 각각이 수량을 갖기 떄문에 Zset으로 해결하기에는 어려움이 많았습니다. 때문에 싱글스레드로 Zset을 통해 해결하기보다는 여러 상품들의 재고 로직이 진행되는 동안 락을 걸어 진행하는 것이 맞다고 생각했습니다.

 

https://ndm-tech.tistory.com/88

 

재고 처리 로직 동시성 이슈 해결 일지 2

동시성을 해결하기 위해 Redis를 선택하게 된 생각의 과정은 앞선 포스팅에서 정리하였습니다. https://ndm-tech.tistory.com/34 4. 재고 처리 로직 동시성 이슈 해결 일지 배달 어플을 구현하는 SlowDelivery

ndm-tech.tistory.com