문제 상황 및 해결 과정
1. 결제 시스템(Pay-Transaction-Payment)
결합도
상황: 결제/환불 요청 흐름에서 로직간 결합도가 높았음
문제: 유저가 결제를 요청하면 성공/실패 응답 받기까지 시간 오래 걸림
해결: 트랜잭션 생성 완료되자마자 유저에게 결제 상태 변경(pending) 반환
트랜잭션 성공 → Settlement, Notification, Schedule 서비스는 kafka로 통신
Pay Service가 죽었을 경우?
•
상황: 유저가 결제 요청 → (Transaction Service → …) 로직 진행 중에 Pay Service 죽음
⇒ 유저가 결제 성공/실패 어떻게 알 건지
◦
잘못하면 결제 업체가 돈 먹을 수 있음
⇒ Pay Service가 죽었는지 살아있는지 매번 확인? retryCount?
외부 클라이언트
상황: 내부 결제 시스템만 생각해서 단순히 결제하는 내부 클라이언트가 요청하는 것으로 생각
결제 과정 프론트엔드가 존재하는데 결제 클라이언트와 별개로 생각 X
= (클라이언트) 결제 과정 페이지에서 결제 요청 → (서버) 결제 처리라고 생각
⇒ (외부 클라이언트) 결제 요청 → (결제 과정 클라이언트) 결제 페이지 → (결제 서버) 결제
문제: (외부 클라이언트 → 결제 과정 클라이언트 → 서버) 로직은 보안 위험
최종적으로 결제 완료 후 외부 클라이언트에게 반환했을 때도 외부 클라이언트가 자신의 서버에 결제 안된 것처럼 속일 위험 있음
해결: (외부 클라이언트) 결제 요청 → (외부 서버) 유저인증, 결제 요청
→ (결제 서버) 결제, 리다이렉트 (결제 과정 클라이언트) 결제 성공/실패 응답
실제 결제할 때 한 번 더 확인?
결제 요청을 하고 카드 어플이나 웹사이트로 결제 확인을 하고 다시 결제 요청 화면으로 돌아왔을 때
유저가 체크 표시 O → “진짜” 결제 / X → 결제 취소
⇒ 결제 요청을 한 것 자체는 트랜잭션에 기록 되어야 함(상태 변경: Pending → Succss/Faild/Canceld)
이 때, 유저의 계좌에서 진짜 실제 돈을 빼서 들고 있을까 아님 시스템상으로만 들고 있을까
이 경우 만약 유저가 체크 표시 화면에서 나가서 다른 상품을 결제하면 어떻게 될까?
⇒ 유저가 체크 표시를 하고 최종 버튼을 눌러야 “진짜” 결제가 될 때 실제 돈이 빠져나가야 함
결제 요청 금액을 어떻게 관리하는가
•
은행의 역할까지 수행 → 고객 계좌에 holding된 금액을 별도 관리 → 금액 이상만큼 다른데서 못찾아가게 처리
•
플랫폼의 역할만
◦
진자 결제를 할때 돈을 차감해오는 과정에서 돈 부족하다면 모두 롤백처리
⇒ 최종 결제시에는 무조건 해당 금액이 있는지 체크하고 없다면 롤백
→ 이전: pay service에서 결제 진입 시에만 1차 금액 확인 후 금액이 없다면 결제 X
→ 수정: payment service에서 실제 결제(최종 결제)시에 다시 한 번 해당 금액 확인
상황: 결제 요청하면 바로 알아서 흘러가서 결제되는 형식
문제: 결제할 때 결제 프론트엔드에서 최종적으로 결제 버튼을 눌러야 “진짜” 결제가 됨
즉, 최종 결제 버튼 누르지 않고 나가면 결제 취소
해결: 로직 한 번 더 분리
•
v1/payment 처음 결제 요청 → 트랜잭션 Pending 상태, 결제 프론트엔드로 리다이렉트
•
v1/payment/verify 리다이렉트 url에 있던 토큰으로 상태 확인
•
v1/payment (결제 프론트엔드) 최종 결제 버튼 → 트랜잭션 ID 찾고 실제 결제 로직
•
payment service에서 최종 결제 전에 한 번 더 페이에 돈이 있는지 확인
◦
현재 로직에서는 pay service에 account, pay, point가 있어서 이렇게 그림이 나올 수 밖에 없음
포인트 및 실결제
상황: 포인트 사용, 결제 API가 각각 존재
•
PUT /v1/pay/points/use
•
POST /v1/pay/payment/{sellerId}
클라이언트가 결제 전 포인트 사용 API 호출 → (원래 금액 - 포인트 사용 금액) 결제 API 호출
문제: 클라이언트에서 포인트 사용 API를 호출하지 않고 (원래 금액 - 포인트 사용 금액)을 넣은 결제 API 호출할 수 있음
해결: 포인트 사용은 결제할 때만 사용 → 결제 로직 내부에서 포인트 사용까지 처리
계좌 예외 처리
상황: 계좌 생성, 수정, 삭제 API 존재
문제: 페이는 무조건 한 개 이상의 계좌를 가지고 있어야 함
같은 계좌 번호가 여러 개 등록되는 상황(은행API를 연결하지 않았기 때문)
해결: 페이 생성 시 계좌 생성 로직 재활용하기
커스텀 예외 처리 활용하기: 같은 계좌 번호 사용 X 계좌가 1개일 때 삭제 X
취소 및 환불
한 서비스 내에 로직이 많아지는 것 vs. 통신이 많아지는 것 → 어떤 게 효율적?
각 서비스가 어디서부터 어디까지 담당할 것인가 명확하게 표현되어야 함
상황: payment service와 refund service 분리
•
payment service: 결제
•
refund service: 취소와 환불
해결: http 통신이 적어서 더 효율적이라고 판단 → payment service와 refund service 통합
2. Acitivity History
Admin Service에는 관리자(manger, admin)의 행동 히스토리 작성 (OpenFeign 사용)
상황: Item/Funding Service에서 Admin Service(ActivityHistory) 호출해서 작성
→ v1/items, v1/fundings
문제: Admin Service가 죽으면 Item 서비스에도 영향 O
해결: Admin Service에서 Item Service 호출
→ Admin Service가 과부화될 위험 있음 But, 죽어도 다른 서비스에 영향 X
→ URI가 v1/admin/으로 시작하게 할 수 있음
•
AdminItemController: v1/admin/items
•
AdminFundingController: v1/admin/fundings
3. 관리자
상황: 관리자와 유저의 URI가 같고 안에 서비스 로직에서 권한 확인 후 각각의 결과 반환
문제: 관리자와 유저는 반환 값이 다름
해결: 유저는 Page, 관리자는 List로 반환, URI 분리
•
단, 유저는 상황마다 다름
◦
펀딩 리스트는 정보량이 많아 페이지네이션 적용하기
◦
아이템이나 기부사 리스트는 리스트로 반환
Before
After
4. 코드 분리
서비스 내부에서 작동하는 코드 → Internal 인터페이스로 분리
ex. public class ItemServiceImpl Implements ItemService, InternalService {...}
5. 아이템 반환 with 채민
상황: MyItem은 UPDATE가 많아 MyItem 중 현재 사용 중인 아이템은 UsingItem 테이블로 분리(일대일 관계)
사용 중 아이템 한 개마다 서버를 호출하는 구조
•
POST /v1/items/my-items/using-items, PUT DELETE /v1/items/my-items/using-items/{usingItemId}
문제: 서버 요청이 빈번한 상황 → 서버 부하 문제
고민: 새로운 API 만들어서 → action으로 CREATE, UPDATE, DELETE 넣어 보내기
•
/v1/items/my-items/using-items/action
문제: 서버가 action에 다른 값 들어오는 상황을 처리해야 함
고민: 편집 모드를 넣기
편집 버튼 클릭 → 아이템 수정 → 완료 버튼 클릭 + 리스트로 보내기
문제: 서버 부하는 적어지나 사용자가 바로바로 아이템을 수정할 수 없어 유저 친화적 X
해결: GraphQL 도입
•
REST보다 네트워크 트래픽 감소
•
하나의 mutation → 단일 엔드포인트 ⇒ 복잡한 작업(CREATE, UPDATE, DELETE) 처리
•
subscription 기능 사용 → 실시간으로 UI에 반영
6. 채팅방
상황: 채팅 AI 모델 SpringBoot 서버(WebSocket) 클라이언트
→ 실시간성, 다중 사용자 동시 처리 및 성능이 중요함
문제
1.
동기적 I/O 사용하는 Tomcat으로 설계 → 동시 요청 처리량 제한 ⇒ 성능 저하
2.
비효율적인 비동기 처리
•
Thread.sleep(500): 메시지 응답 생성에서 불필요한 블로킹 호출
•
CompletableFuture.runAsync
: 별도의 스레드풀 X or 최적화 X → 요청량 ↑ ⇒ 병목 현상
3.
AI 응답 생성 병목
•
RestTemplate 사용해 동기적으로 처리 → 호출량 ↑ ⇒ 병목 현상
해결
•
WebFlux + Netty 사용
◦
WebFlux: 비동기 논블로킹 방식
◦
Netty: 적은 스레드로 높은 동시 요청 처리할 수 있음
•
AI 응답에 gRPC 사용
◦
네트워크 오버헤드 줄이고 요청-응답 시간 단축
▪
HTTP/2
◦
Protocol Buffers → 데이터 직렬화 최적화
▪
JSON/텍스트 기반 프로토콜보다 데이터 크기 작고 전송 속도 빠름
◦
Kafka도 비동기
▪
메시지 브로커 거쳐 메시지 전달 → 지연 발생
▪
이벤트 기반 동작 → 소비자 지속적으로 polling하는 구조 추가로 구현
▪
⇒ 수십만 명 사용자 동시접속하는 대규모 채팅 애플리케이션에서 확장성 위해 사용 O