[리팩토링] TrendPick 프로젝트 정산 프로세스 이슈 해결 및 리팩토링 과정 / 정산 프로세스 개편

2023. 8. 25. 17:36기타 (회고 및 정보글 등)

[정산 시스템이란 ?]

기존 정산 프로세스는 연월(yyyy-MM) 정보를 기준으로 해당 월에 생성된 주문 상품을 복제하여 정산 데이터를 생성한다.

 

그래서 정산 데이터를 생성한 후 선택한 정산 데이터를 정산 처리하면 브랜드 관리자에게 수수료를 제외한 캐시가 생성되고 이를 출금할 수 있는 시스템이다.


[이슈발생]

정산데이터 생성 후 정산 처리 시 500에러 발견

-> 이미 정산 처리가 되었음에도 다시 정산 데이터가 생성되어 중복이 발생하기 때문

 

정산 처리가 되었음에 불구하고 출금 가능 캐시가 그대로

-> 정산 처리한 회원이 어느 <브랜드 관리자> 회원인지를 찾아서 캐시를 처리하는 과정에서 오류가 발생

 

해당 오류를 처리하기 위해 코드 리팩토링 중 '정산' 이라는 도메인 역할에 이상을 느꼈다.

 

'정산' 이란 '월 정산', '연 정산' 등으로 나누어져서 정밀하게 계산하거나 집계를 내리는 것이라고 생각하는데, 현재 구현되어 있는 정산은 단순하게 주문에 관한 정보를 전부 RebateOrderItem에 몰아넣고 끝이다.

 

따라서 정산 프로세스를 명확하게 정의하고, 그에 따라서 코드 구조를 리팩토링 해야겠다고 생각했다.


[리팩토링]

해당내용 Github PR

 

Fix/431 rebate and withdraw refactoring by jooooonj · Pull Request #432 · TrandPick/TrendPick_Pro

내용 정산데이터 생성 후 정산처리에서 생기는 이슈를 해결했습니다. 정산 후 캐시가 쌓이지 않던 이슈를 해결했습니다. 정산 -> 캐시 -> 출금 프로세스를 재정리하였습니다. brand_admin과 admin을

github.com

리팩토링 목적

- SRP를 적용함으로써 코드의 복잡도 줄이기

- 가독성 향상 (변수명 변경 등)

- 내부로 숨길 수 있는 코드를 찾아 캡슐화

정산 -> 캐시 -> 출금 프로세스 정리

  • 스토어(브랜드관리자 계정)는 yy/mm 별로 정산 데이터를 생성하고 조회할 수 있다.
  • 정산은 주문결정완료 상태 (주문완료/배송완료)인 데이터만 실제 정산 처리가 가능하다.
  • 정산처리는 총 관리자가 확인할 수 있는 CashLog로 기록을 남기고 수수료 정산 후 스토어에게 캐시로 변환됩니다.
  • 스토어(브랜드관리자 계정)는 해당 캐시를 출금요청이 가능하다.
  • Admin(총관리자) 는 출금을 승인할 수 있다.

큰 틀을 위와 같이 명확하게 정의한 후 코드 리팩토링을 시작했다.

 

다음은 리팩토링 한 코드의 일부이다.

== 기존 코드 ==
if (withdrawApply == null) {
            return RsData.of("F-1", "출금신청 데이터를 찾을 수 없습니다.");
        }
        long restCash = memberService.getRestCash(withdrawApply.getApplicant());

        if (!withdrawApply.isApplyDoneAvailable()) {
            return RsData.of("F-2", "이미 처리되었습니다.");
        }
        if (withdrawApply.getPrice() > restCash) {
            return RsData.of("F-3", "예치금이 부족합니다.");
        }
        Brand brand=brandService.findByName(withdrawApply.getApplicant().getBrand());

        CashLog cashLog = memberService.addCash(
                        withdrawApply.getApplicant().getBrand(),
                        withdrawApply.getPrice() * -1,
                        brand,
                        CashLog.EvenType.출금__통장입금
                )
                .getData().getCashLog();

위 코드는 하나의 메소드 내에 있는 코드이다.

 

유효성 검사, 객체 생성 등이 복잡하게 얽혀서 읽기조차 어렵게 느껴진다.

 

== 개선 코드 ==
RsData validateResult = validateAvailableCancel(withdrawApply); //취소 가능한지 검증
if (validateResult.isFail()) return validateResult;
 CashLog cashLog = cashService.addCashLog(withdrawApply);

결국 해당 코드에서는 유효성을 검증하고 캐시로그를 생성하는 로직을 가지고 있다.

 

따라서 위와 같이 유효성을 검증하는 모듈을 분리하여 빼놓아 가독성을 향상시킬 뿐 아니라 SRP를 적용시킬 수 있다.

 

그리고 해당 로직이 있는 서비스는 WithdrawService 이다. 

 

그런데 CashLog를 생성하고 저장하는 것도 해당 클래스에서 생성하면 SRP 에 위배된다.

 

따라서 CashLog 생성과 저장은 CashService에게 위임함으로써 결합도를 낮출 수 있었다.

 

SRP를 적용하고, 캡슐화시키고, 변수명을 신경써서 짓는 것만으로 코드의 복잡도가 많이 내려감을 볼 수 있다.

 

이러한 부분을 위주로 정산 프로세스에 포함되는 Rebate, CashLog, Withdraw 도메인의 코드를 하나씩 뜯어보며 리팩토링을 진행했다.


 

[정산 프로세스 개편]  

해당 내용 Github PR

 

Refactor/439 정산 프로세스 오류 수정 및 개편 by jooooonj · Pull Request #440 · TrandPick/TrendPick_Pro

개편 내용 월 정산 데이터 (MonthRebateData) 도입 RebateOrderItem의 데이터를 모아서 월 통계 데이터를 저장 Store와 ManyToOne 매핑 매월 1일 모든 Store에 대해 자동으로 객체 생성 정산 프로세스 정리 주문

github.com