소 잃고 뇌약간 고친 MSA 구현기(feat. 결제 시스템)

올랜도·2023년 5월 23일
20
post-thumbnail

최근 6인 프로젝트 Next-Novel에서 결제 도메인에 대해 MSA기반 개발을 마쳤다.

사실 제대로 이루어졌는지 떠올려 본다면 애매모호하다는 느낌이 든다.

대충 이런느낌이었던 것 같은데, 어떻게 개발해나갔는지 한번 복기해보고자 한다.

1. 도메인 분리


앞서 언급했듯이 나는 Next-Novel의 결제 시스템을 담당했다.
결제시스템은 기존의 서비스를 고치는게 아닌 기능 추가의 영역이었기 때문에 새로운 ERD가 필요했고, 설계를 위해 몇 가지 사항을 고려했다.

  1. 결제까지의 과정이 어떻게 이루어지는가?
  2. 1번의 과정에서 요구되는 도메인은 어떤것이 있는가?
  3. 도메인간의 연관관계는 어떻게 구성되는가?

일단 내가 생각한 결제의 과정은 다음과 같았다.

상품 선택 -> 상품 주문-> DB 내 주문 생성 -> DB 내 주문 상품 추가

이를 위해서는 상품, 주문, 주문상품이 필요하다고 생각했고 우리팀에서 사용하고자 하는 상품은 포인트였기 때문에 포인트 관리를 위한 포인트 도메인이 필요했다.

마지막으로 연관관계는 포인트(유저)가 다수의 주문을 가질 수 있고, 주문에는 단일 개수의 상품이 들어가며 별도의 주문 상품이 필요하다고 생각했고 이에 맞게 ERD를 구성했다.

위의 내용 중 파란색으로 표시되어있는 부분처럼 결과가 나왔는데, 생각만큼 많은 내용을 갖고있지는 않았지만 구현하는 과정이 심상치 않을것임을 새삼 느꼈다.

2.프로젝트 구조 설계


사실 처음에는 프로젝트를 모놀리식으로 만들었다. 열심히 API를 만들어내던 중 옆에 있던 팀원이 나에게 이렇게 말했다

너가 만든 도메인, MSA로 한다면 다 쪼개야 하는거 아니야?

아차차! 우리팀이 각각 맡았던 회원, 소설, 결제 도메인을 그냥 Spring cloud에 올려서 요청 처리를 하는 것이 MSA라고 생각했다.

하지만 생각해보니 각각의 도메인간의 데이터 통신이 없었기에 이건 그냥 팀원마다 하나씩 별개의 프로젝트를 만드는 것임을 알게 되었다.

듣고나니 금방 고개를 끄덕이게 되었고 내가 만든 모놀리식 프로젝트에서 각각의 도메인을 1번에서 소개했던 것과 같이 도메인별 프로젝트를 구성했다.

PaymentsService
├─core
│  └─src
│
├─item-service
│  └─src
│   
├─order-item-service
│  └─src
│         
├─order-service
│  └─src
│  
└─point-service
    └─src

공통 로직(예외, PG서비스)를 처리하기 위한 Core모듈과 각각의 모듈을 정의하니 위와 같은 구조의 Multi-Module기반의 프로젝트가 되었다.

3. BootPay 연결하기


결제를 위한 PG 플랫폼으로 Boot Pay를 선택했다.
카카오페이를 비롯하여 다양한 결제 수단을 제공하거니와 UI가 단순하였고 연동과 관련하여 공식문서를 잘 제공했기 떄문에 해당 플랫폼을 사용하여 내가 만든 프로젝트에 연결을 수행했다.

BootPay에서 제공하는 결제 로직은 다음과 같았으며 별도의 Spring 컴포넌트에 다음과 같은 메서드를 구현했다.

  1. 토큰 발급 (connectBootPay)
  2. 주문 유효 검증 (validateOrder)
  3. 주문 확인 (confirmOrder)
  4. 주문 취소 (cancelOrder)

이렇게 구성된 컴포넌트를 Service레이어에 DI하여 사용했다.

4. 결제 프로세스 구현


내가 생각한 구현의 과정은 MSA의 Saga 패턴, 그중 Orchestrator패턴을 참조한 구현이었다.
구현을 위한 Orchestrator와 통신을 위한 Message Queue가 필요했고 Apache Kafka를 사용했다.

전반적인 프로세스는 위와 같이 설계를 수행했으며 데이터 통신을 위해 단일 partition을 갖는 3개의 topic을 만들어 개별 서비스에 메시지를 pub/sub을 수행하였다.

4.1 겪었던 문제.


진짜 docker내에서 컨테이너끼리 통신안되는거 왜그러는거시는거에요

발단

로컬 환경에서 kafka 컨테이너를 띄우고 Spring boot 프로젝트를 실행시켰을 때 Spring boot와 kafka에서 생성한 토픽간의 연결이 잘 이루어 졌다.
하지만 ec2서버에 올라간 Spring boot와 kafka 컨테이너간의 토픽 연결이 disconnect되는 현상이 발생했다.

원인 분석

처음에는 컨테이너에서 다른 컨테이너로의 연결이 안되는 것이라 생각하여 다음과 같은 방식의 해결을 시도했다.

  1. docker-compose 내 네트워크 설정
  2. kafka 컨테이너 내 네트워크 설정
  3. spring boot의 kafka 호스트 설정

대충 세가지 방식으로 문제를 해결하려다 보니 사흘정도 시간을 보냈는데, 놀랍게도 원인은 주말을 보낸 후, 월요일에 팀원이 답을 알려주었다.

해결

Orlando님, 그거 빨리 말씀하시지 그랬어요.
그거 Docker network에서 같은 네트워크 그룹에 넣어주면 되는건데!!

어떻게든 혼자서 문제를 해결하려고 하는 습관이 오히려 문제 해결에 발목을 잡은셈이었다.

약간의 자아성찰 시간을 갖고 팀원이 알려준 내용을 토대로 network에 spring boot 컨테이너와 kafka 컨테이너를 추가해 주었더니 곧바로 해결이 되었다.

마냥 행복했다.

5. 근데 이제 뭐함?


사실 Saga패턴의 Orchestrator를 적용하는 과정에서 실수한게 하나 있다.
바로 보상정책을 구현하지 못했다는 것이다.
트랜잭션 과정에서 문제가 발생할 경우 트랜잭션 취소와 관련된 로직이 들어가야 하는데, 4.1에서 언급한 문제를 해결하는데 소모되는 비용이 너무나 크게 소요되어 추가적인 로직을 작성할 시간이 없었다.

아마 조만간 주문 취소와 관련된 보상정책 로직을 추가하고 Orchestrator를 다시 리팩토링하는 시간을 갖지 않을까 싶다.

그리고 여러모로 깨달은게 많았던 것 같은데
MSA 방식으로 프로젝트를 구현하니 그냥 모놀리식으로 API를 만드는 것에 비해 독립적으로 프로젝트를 실행할 수 있다는 점에서 운영상에 이점을 가져갈 수 있겠구나 싶었으며, JPA에서 제공하는 강력하고 편한 기능을 배제하고 트랜잭션과 관련된 프로세스에 대해 고민할 수 있는 시간이 나를 조금 더 나은 개발자로 만들어주는 과정이었음을 깨달았다.

profile
☂️생존주의 개발자

2개의 댓글

comment-user-thumbnail
2023년 5월 23일

로켓이 부활할 수 있길 바랍니다!

답글 달기
comment-user-thumbnail
2023년 5월 23일

카프카 마스터 올란도!

답글 달기