파트너 포탈에 결제 도메인 신규 기능 추가
목차
파트너 포탈 결제 도메인에 신규 기능을 추가했다. 대시보드 KPI 카드 가독성 개선(폰트 대형화 + 정렬)이 출발점이었는데, 막상 손대다 보니 뷰/스타일 파일 한 개로 끝날 작업이 아니었다. 기존 화면과 API에서 제공하지 않던 데이터와 동작이 필요했고, 그게 곧 쿼리 레벨부터 다시 설계해야 한다는 뜻이었다.
왜 "뷰 한 개"로 안 끝나는가
결제 도메인 포탈 기능은 겉보기엔 화면에 숫자 하나 더 보여주는 일처럼 보인다. 근데 실제로 파고들면 거의 항상 아래 레이어가 다 엮인다.
- SQL 집계: 기존 쿼리로 원하는 숫자가 안 나오면 뷰나 서브쿼리를 새로 짜야 함
- 상태 머신: 결제 상태(대기/완료/취소/환불)가 화면 어느 곳에서 어떻게 표시되는지 일관성 유지
- 예외 케이스: 빈 데이터, NULL, 권한 없는 접근 각각 방어 로직 필요
- 권한 체크: 인터셉터가 해당 URL 패턴을 커버하는지 사전 확인
- 사이드바 메뉴 활성화: active 클래스 조건이 맞지 않으면 사용자가 현재 어디 있는지 파악 불가
이번에도 순서대로 다 거쳤다. 백엔드 API 엔드포인트 구현, 프론트 JSP 화면 연동, AJAX 호출 처리, 권한 인터셉터 통과 확인, 사이드바 활성화 처리. 어느 하나라도 빠뜨리면 특정 사용자에게 이상한 화면이 나타나거나 숫자가 안 맞는다. 단순 UI 추가가 아니라 쿼리 레벨부터 정합성을 맞춰야 했던 이유가 여기 있다.
설계에서 실제로 고민한 것들
데이터 정합성이 금융 도메인에서 가장 먼저 체크해야 할 항목이다. 새 화면에서 보이는 집계 숫자가 기존 화면 값과 다르면 그 순간 신뢰가 깨진다. 쿼리를 새로 짤 때는 반드시 기존 화면과 동일한 조건으로 돌려보고 숫자가 일치하는지 확인했다.
실시간 갱신 여부는 기능을 설계할 때마다 결정이 필요한 지점이다. 폴링이냐 이벤트냐. 이번 KPI 대시보드는 준실시간으로 충분해서 단순 AJAX 폴링으로 처리했다. 선택 기준을 정리하면 이렇다.
| 기준 | 폴링 | 이벤트(WebSocket/SSE) |
|---|---|---|
| 데이터 변경 빈도 | 낮음-중간 | 높음 |
| 구현 복잡도 | 낮음 | 높음 |
| 서버 부하 패턴 | 고정적 (주기마다 쿼리) | 변동적 |
| 적합한 상황 | 대시보드 KPI 갱신 | 실시간 알림, 거래 스트림 |
결제 대시보드 KPI는 폴링이 훨씬 현실적이다. 이벤트 기반으로 가면 인프라 복잡도가 올라가는데 그만큼 이득이 없다. 기술적으로 가능하다고 해서 무조건 쓰는 게 아니라, 요구사항이 정말 그 복잡도를 정당화하는지 먼저 따져봐야 한다.
쿼리 실행 계획은 새 집계 쿼리를 붙이기 전 반드시 확인했다. EXPLAIN 찍어보고 풀 스캔 없는지, 인덱스 제대로 타는지 확인. 결제 테이블은 데이터가 빠르게 쌓이기 때문에 처음에 실행 계획이 깨끗하지 않으면 나중에 느려지는 시점이 반드시 온다. 그때 가서 고치면 이미 운영 중인 기능을 건드려야 해서 리스크가 훨씬 커진다.
-- 결제 상태별 집계 쿼리, 실행 계획 먼저 확인
EXPLAIN
SELECT status, COUNT(*) AS cnt, SUM(amount) AS total
FROM payments
WHERE created_at >= '2026-04-01'
GROUP BY status;
-- type=ALL(풀스캔)이면 created_at 인덱스 있는지 확인
-- 인덱스 없으면 추가하거나 파티셔닝 검토
NULL 처리와 예외 방어는 JSP + AJAX 환경에서 흔히 놓치는 부분이다. 백엔드에서 데이터가 없을 때 null을 그냥 반환하면 프론트에서 undefined로 렌더링되거나 NaN이 화면에 찍힌다. 결제 금액 자리에 NaN이 뜨면 사용자 입장에서 시스템 전체를 못 믿게 된다. 이번엔 아래 패턴으로 방어했다.
// AJAX 응답 처리 - null/undefined 방어
success: function(data) {
var amount = (data && data.totalAmount != null) ? data.totalAmount : 0;
var cnt = (data && data.count != null) ? data.count : 0;
$('#kpiAmount').text(numberFormat(amount));
$('#kpiCount').text(cnt);
},
error: function() {
// 통신 실패 시에도 이전 값 유지, 아무것도 안 하는 것보다 낫다
console.warn('KPI 갱신 실패 - 이전 값 유지');
}
백엔드에서도 Optional 처리나 기본값 반환을 명시적으로 해줘야 한다. 프론트-백 양쪽에서 방어하는 게 중복처럼 보여도, 하나가 뚫렸을 때 나머지가 막아준다.
검증과 개발 습관
구현 후 직접 화면에서 동작 확인했고, 기존 데이터가 깨지지 않았는지 관련 화면 숫자를 cross-check했다. 변경 전 수치를 메모하고, 수정 후 동일한 케이스로 재확인하는 흐름을 루틴으로 유지 중이다. 번거롭지만 이 단계를 생략하면 나중에 "언제부터 이상했는지 모르겠다"는 상황이 생긴다.
커밋은 논리적으로 독립된 단위로 쪼갠다. 쿼리 추가, API 엔드포인트, JSP 연동, 권한 처리를 한 커밋에 다 때려 넣으면 나중에 버그가 생겼을 때 어느 변경에서 깨졌는지 추적하기 힘들어진다. git bisect를 써보면 커밋 단위가 얼마나 중요한지 바로 체감된다. 작은 커밋을 자주 하는 게 디버깅 비용을 가장 많이 줄여주는 습관 중 하나다.
커밋 메시지도 "무엇을"보다 "왜"를 담으려 한다. JSP 파일 수정 보다 결제 KPI 카드 NULL 방어 처리 추가 - 빈 데이터일 때 NaN 표시 방지 쪽이 6개월 후에 훨씬 쓸모 있다. 본인이 쓰고도 무슨 의도였는지 모르는 커밋 메시지는 쓴 게 아니다.
사내 서비스라도 결제 도메인은 꼼꼼함이 기본값이어야 한다. 숫자 하나가 틀리면 그게 신뢰 문제로 이어지고, "대충 맞는 것 같다"로 넘어간 것들은 나중에 반드시 다시 돌아온다. 확인을 귀찮다고 줄이는 순간이 가장 위험한 순간이다.
댓글 0
첫 댓글 달아줘.