파트너 포탈에 하위 파트너 정산
목차
partner-portal에 하위 파트너 정산 기능을 추가했다. 변경된 파일은 SQL 매퍼 2개, 내부 클래스 1개, 뷰/스타일 1개. 숫자만 보면 작은 작업처럼 보이는데 고려해야 할 게 꽤 많았음.
왜 쿼리 레벨부터 다시 설계했나
기존 파트너 포탈은 최상위 파트너 기준의 데이터만 내려주는 구조였다. 단일 계층에서는 문제없었는데, 하위 파트너가 생기면서 파트너별 잔액 변동을 집계해서 보여줘야 하는 요구가 생겼음. 기존 API에 필터 파라미터 하나 얹는 방식으로는 답이 안 됐다.
집계 방식 자체가 달랐기 때문이다. 상위 파트너 정산은 건별 트랜잭션 합산이고, 하위 파트너 정산은 파트너 단위로 GROUP BY를 먹인 별도 집계였다. 같은 테이블을 다른 관점으로 보는 거라 기존 매퍼를 억지로 재사용하면 조건이 지저분해지고 실행 계획도 나빠질 가능성이 있었음. 그래서 SQL 매퍼를 분리했다.
-- 기존: 건별 합산
SELECT SUM(amount) FROM settlement WHERE partner_id = #{partnerId}
-- 신규: 하위 파트너별 집계
SELECT
sub.partner_id,
sub.partner_name,
COALESCE(SUM(s.amount), 0) AS total_amount,
COALESCE(SUM(s.fee), 0) AS total_fee,
COUNT(s.tx_id) AS tx_count
FROM partner sub
LEFT JOIN settlement s
ON s.partner_id = sub.partner_id
AND s.settled_at BETWEEN #{fromDate} AND #{toDate}
WHERE sub.parent_partner_id = #{parentPartnerId}
GROUP BY sub.partner_id, sub.partner_name
실제 컬럼명은 다르지만 구조는 이랬다. LEFT JOIN을 쓴 이유는 정산 이력이 없는 하위 파트너도 행으로 나와야 해서임. INNER JOIN하면 이력 없는 파트너가 목록에서 아예 빠져버려서 "없음"인지 "0원"인지 구분이 불가능해진다.
구현하면서 부딪힌 지점들
권한 인터셉터. 포탈에 신규 URL이 생기면 인터셉터 매핑을 반드시 확인해야 한다. 사이드바에서 메뉴가 안 보여도 URL을 직접 치면 접근이 가능해서, 인터셉터가 막지 않으면 권한 없는 사용자에게 데이터가 그냥 내려간다. 이번엔 기존 역할 기반 패턴을 그대로 따라서 처리했고, 구현 후 권한 없는 계정으로 URL 직접 접근을 시도해서 실제로 막히는지 확인했음.
폴링 vs 이벤트. 잔액 변동 집계가 실시간으로 갱신돼야 하는지 결정이 필요했다. 정산 데이터가 배치 처리 후 확정되는 구조였기 때문에 중간 단계 숫자가 화면에 노출될 이유가 없었음. 그래서 화면 진입 시 1회 조회 + 수동 새로고침으로 결론 냈다.
| 방식 | 장점 | 단점 | 채택 |
|---|---|---|---|
| 폴링(인터벌 AJAX) | 별도 인프라 불필요 | 불필요한 요청, 배치 중간 상태 노출 위험 | X |
| WebSocket / SSE | 즉각 반영 | 구현 복잡도, 이 규모에 과잉 | X |
| 진입 시 1회 조회 | 단순, 확정 데이터만 노출 | 수동 새로고침 필요 | O |
NULL 처리. 쿼리에서 COALESCE로 처리하고 VO에도 기본값을 명시했다. 한 쪽에서만 막으면 다른 진입로로 들어올 때 0 대신 null이 내려가는 경우가 생겨서 두 레이어 모두 잡아뒀음.
JSP + AJAX 연동. 응답 JSON을 테이블로 동적 렌더하면서 숫자 포맷(천 단위 콤마)은 프론트에서 처리하도록 뒀다. 백엔드에서 포맷된 문자열을 내려주면 프론트에서 정렬이나 합계 계산할 때 파싱이 필요해지기 때문에 raw 숫자를 내려주는 게 낫다고 판단했음.
검증은 cross-check가 핵심. 구현 후 제일 먼저 확인한 건 기존 화면과 숫자 일치였다. 상위 파트너 정산 화면의 합계와, 이번에 추가한 하위 파트너별 집계를 더한 값이 맞는지 대조했음. 일치하지 않으면 집계 기준이 다르거나 중복/누락이 있다는 뜻이라 기능이 있어도 신뢰할 수 없다.
검증 순서는 대략 이랬다.
- 변경 전 기존 화면 수치 메모
- 신규 화면 데이터 조회 후 합계 계산해서 기존 화면 합계와 대조
- 빈 데이터, 날짜 경계값, 하위 파트너 없는 케이스 수동 확인
- 권한 없는 계정으로 신규 URL 직접 접근
작업 후기
파트너 포탈처럼 계층 구조가 들어가면 "기능 추가"의 무게가 달라진다. SQL 집계 기준, 권한 레벨, 화면 상태, 예외 처리가 모두 계층을 인식하고 있어야 해서 어느 레이어 하나만 빠뜨려도 특정 파트너에게만 이상한 화면이 나오는 케이스가 나온다. 금융/결제 도메인은 숫자 하나가 틀리면 신뢰가 무너지는 영역이라 "대충 맞는 것 같다"로 넘기면 나중에 반드시 돌아온다. 실제로 집계 단위 하나를 잘못 이해하면 특정 날짜 범위에서만 값이 튀는 버그가 생기는데, 그게 발견되는 타이밍이 항상 제일 불편한 시점이더라.
이런 작업일수록 커밋을 잘게 쪼개는 게 중요하다. SQL 매퍼 추가, VO 추가, 컨트롤러/서비스 연결, 뷰 연동, 권한 처리 - 이걸 한 덩어리로 올리면 나중에 어느 변경이 문제인지 추적이 훨씬 어려워진다. 논리적으로 독립된 단위로 쪼개면 revert도 쉽고 리뷰도 쉽다.
커밋 메시지도 "SQL 매퍼 추가"가 아니라 "하위 파트너 집계 쿼리 분리 - 기존 매퍼 재사용 시 실행 계획 악화 우려"처럼 왜를 담으려고 한다. 몇 달 뒤 git log 볼 때 과거의 나에게 고마워지는 포인트가 바로 이 부분이라서.
댓글 0
첫 댓글 달아줘.