주문 API 회고

ㅎㅎ·2023년 10월 3일
0
  • 주문테이블의 PK는 어떻게 관리했는지?
  • 주문의 트랜잭션 처리는 어떻게 했는지?
  • 스프링에서 트랜잭션이 동작하는 원리는 무엇인지?
  • 사실 구현에 급급해 가게 1개를 고정해놓고 그곳에 주문 및 결제만 할 수 있게 했고, 주문 취소 API도 없었다. 그래서 위 질문에 대한 답과 함께 제대로 설계하려면 어떻게 했어야 하는지 생각하면서 간단하게 리팩토링을 해보고자 한다.

1. 주문 PK는 가게 별로 어떻게 관리하지?

  • 우선 우리 프로젝트에서는 단순하게 MySQL의 Auto Increment로 주문 테이블의 PK를 관리하고, 이를 각 주문의 ID로 사용했다. 이 질문의 의도가 뭐였을까 잘 모르겠어서 주문 엔티티 설계와 관련해서 정보를 찾아봤다. 다른 곳은 주문 ID를 어떻게 관리하지?

  • PK로 Auto-Increment를 사용하는 방안과 UUID를 사용하는 방안이 있다고 한다.

    • UUID를 사용할 때의 장점
    1. 분산 DB를 이용하는 등의 환경에서도 PK가 고유값인 것이 보장되어 데이터를 합칠 일이 발생하거나 할 때 PK를 건드리지 않아도 된다.
    2. 데이터에 대한 정보를 노출하지 않는다. 예를 들어 주문 ID가 100번이면 이 상품이 100번 주문됐구나 하는 등 비즈니스적인 데이터를 노출할 우려가 있는데, 이것에서 안전하다.
    3. DB를 접근하지 않고도 새로운 ID를 생성할 수 있다.

    • UUDI의 단점(장점만 있는 것이 아니다!)
    1. 기본적으로 AI 사용할 때보다 값이 크기에 저장소를 많이 차지하고, 인덱싱도 느릴 것이다.

    참고: UUID와 increment PK는 언제 사용해야할까?

  • 이 정보를 참고했을 때는 내부적으로 AI를 사용하고, 외부적으로 UUID를 사용하는 방법이 권장되는 거 같다. 그러면 주문 테이블에 UUID를 저장해야하나? 아니면 PK 값으로 UUID를 찾아내는 어떤 함수를 사용해냐 하나?

  • 추가로 우아한 기술블로그에서 확인한 것은 주문 엔티티의 PK인 ID와 더불어서 orderKey를 필드로 가지고 있었다. 이는 비즈니스적으로 활용하기 위해 사용하는 주문 key이다. 우리 프로젝트에서는 사실 가게의 주문 화면에서 주문 번호를 띄우지 않았고, 고객의 주문 내역에서 주문 번호를 띄우지도 않았다. 사실 실제 서비스라면 주문 번호를 가지고 가게에 가서 "저 ~~ 주문했는데요, 주문 번호는 00 이에요" 라고 말하며 주문 확인도 할 것이고, 가게에 전화해서 "00번 주문한 사람인데.." 라고 말하며 주문에 관한 추가 요청을 전달할 수도 있다. 근데 만약 주문 ID PK를 그대로 사용하면? 극단적으로 엄청 주문 데이터가 쌓였다고 가정했을 때 "저 1012312312312312번 주문한 사람인데요.......라고 할 수는 없다.", 이외에도 비즈니스적으로 주문 데이터를 관리하기 위해서 다양한 이유가 있을 수 있기에 주문 번호가 따로 필요함을 느꼈다. 위 참고 블로그에서는 주문ID + 주문일자로 orderkey를 생성해 주고 있었다. 내 생각에는 각 날짜의 주문을 카운트하면서 cntNum+orderDate로 주문 key를 관리하는 것도 괜찮아 보이고 여러 설계적인 측면을 고려해서 추가를 해야할 것 같다.


2. 결제 요청 및 실패 시 처리에 관해

  • 우선 결제 요청은 프론트에서 하는가 백에서 하는가? 우리 프로젝트에서는 프론트엔드에서 카카오페이 API를 호출해 결제를 하고 결제가 성공하면 백엔드로 주문 등록 API를 요청하도록 되어 있음.

    • 카카오페이 API에서 결제 준비 API를 호출해서, 사용자가 결제 정보 입력하여 결제 화면으로 이동할 수 있는 화면을 보여줌
    • 모바일에서 카카오톡을 열고 테스트 결제를 진행
    • 이때 원래는 화면 상 결제 절차를 완료하고 서비스 화면으로 돌아가면 실제 결제를 최종 마무리하는 결제 승인 API를 호출해서 결제가 이루어져야 함
    • 즉 화면상 사용자가 결제 절차 완료 페이지를 본다고 해도 카카오가 redirect_url에 담아준 pg_token을 가지고 결제 승인 API를 해야 최종 결제가 완료되고 사용자에게 결제 완료 카카오톡 메시지를 전달함
    • 하지만 테스트 결제만 진행했기에 결제 승인 API는 이용하지 않음
    • 그냥 결제 요청 절차 마무리 후 결제 완료 화면으로 전환하면서 주문 등록 API 호출
  • 실제 주문 로직 설계

    • 아래 사진은 올리브영 결제 개선 아티클의 그림

    • 우선 결제 요청 및 승인에 관한 결제사 API 호출을 백엔드에서 진행함
      • 아래와 같이 주문에 관한 처리 등이 필요하기에 클라이언트는 결제 대기 화면 상태에 있고, 결제에 관한 데이터를 백엔드로 넘겨서 결제 및 주문 처리를 진행한 후 최종 완료를 프론트에 응답헤줘야 함.
    • 그리고 결제 요청 API가 정상적으로 응답이 오면 주문 관련 로직 처리를 진행함
      • 1.결제까지 완료했는데 서버에서 주문 오류가 나면 돈만 빠져나가고 주문은 요청되지 않는 상황이라 이렇게 로직이 설계되어 있다고 판단됨.
      • 2.또한 만약 재고를 고려해야 하는 등 주문이 가능한지 파악하는 비즈니스 로직이 필요할 수 있기에 이와 관련한 처리를 모두 수행하고 결제 승인 요청을 보냄
      • 이렇게 해야만 결제 에러가 났을 때, 주문이 불가능할 때 등의 경우에 주문 처리를 roll back해 로직을 되돌리고, 결제 취소도 따로 할 필요가 없음.
    • 그러니까 우리 프로젝트는 잘못 설계되었다.

  • 결제 요청할 때 가게 ID가 필요할텐데, 이건 어떻게 관리하지? Store 테이블의 ID?
    • 결제사별로 결제 요청을 할 때 가맹점의 ID가 다 따로 존재한다. 그래서 이 가맹점 ID를 테이블에서 관리하거나 해야 하는 거 같다.

3. 주문 취소와 트랜잭션 처리?

  • 주문 취소는 다소 복잡해보인다.
  • 가게가 주문을 거절해서 취소할 때
    • 주문의 상태를 거절/취소로 변경해야 한다. 그리고 관련 주문 데이터가 변경된 게 있으면(ex 재고) 모두 되돌려야 한다.

    • 주문 등록 처리를 완료하고 결제 승인을 요청하는 과정에서 만약 가게가 주문을 거절해서 주문이 취소되면, 결제가 승인된 상태라면 정상적으로 결제 취소를 요청할 수 있지만 결제승인이 아직 진행중인 상태라면 정상 취소가 불가능하다.

    • 카카오페이 REST API 문서를 보니 결제 취소가 불가능한 상태에 대한 응답 ErrorCode가 있다. 그리고 결제 취소 요청 API를 했을 때 응답으로 현재 결제 진행 상태도 들어온다. 이 정보를 조합해서 백엔드에서 적절하게 에러를 처리해야 할 것이다.


  • 고객이 주문을 취소할 때
    • 고객이 주문을 취소하기 위해서는 우선 가게에서 주문을 수락하기 전이어야 한다. 주문을 수락하기 전이라면 주문 내역에서 취소 버튼이 활성화되어 있어 주문 취소가 가능하다. 아래는 배달의 민족 자주 묻는 질문에 있는 내용인데, 배달의 민족이 이 정책을 사용하고 있다.
    • 고객이 주문을 취소하면, 역시 결제를 먼저 취소한 후에, 정상 취소 되고나서 주문데이터를 주문취소 상태로 변경해야 한다. 그리고 변경된 상태에 대해서 가게에게 실시간 메시지를 전달해서 주문 취소가 됐음을 알려야 한다.

  • 주문 등록 과정에서 에러가 발생하는 경우
    • 주문 등록 비즈니스 로직이 실패하면, 트랜잭션을 통해 조작한 주문 데이터를 모두 roll back 한 후에, 주문이 불가능한 이유에 대한 상태코드를 담아서 클라이언트로 전달해서 적절한 주문 불가능 사유를 띄울 수 있도록 해야 한다.


profile
Hello World

0개의 댓글