쿠폰구매 목록에 결제수단 필터 기능 추가
목차
partner-portal 쿠폰구매 목록에 결제수단 필터를 추가했다. 변경 파일은 내부 클래스 1개, SQL 매퍼 1개, 뷰/스타일 1개. 파일 수만 보면 소소한 작업 같지만 실제로는 쿼리 레벨부터 다시 설계해야 했다.
왜 쿼리부터 손댔냐
기존 목록 API는 결제수단 컬럼을 응답에 내려주고 있었지만 필터 조건으로 쓸 수 있게 설계되어 있지 않았다. 프론트에서 받아온 전체 목록을 클라이언트 사이드에서 걸러내는 방법도 있었는데, 그렇게 하면 페이지네이션이 틀어진다. 1페이지에 20건 보여줘야 하는데 필터 전 20건을 가져와서 클라이언트에서 줄이면 페이지당 건수가 들쭉날쭉해짐. 그래서 처음부터 DB 레벨에서 조건을 걸기로 했다.
SQL 매퍼에서 바꾼 부분은 대략 이렇다.
<select id="selectCouponPurchaseList" parameterType="CouponPurchaseSearchDto" resultType="CouponPurchaseVo">
SELECT ...
FROM coupon_purchase cp
WHERE 1=1
<if test="paymentMethod != null and paymentMethod != ''">
AND cp.payment_method = #{paymentMethod}
</if>
ORDER BY cp.created_at DESC
LIMIT #{pageSize} OFFSET #{offset}
</select>
MyBatis <if> 동적 쿼리로 처리했다. 필터 값이 없으면 전체 조회, 있으면 조건 추가. 단순하지만 이게 제일 낫다. 별도 쿼리를 두 벌 만들거나 CASE WHEN으로 분기치는 것보다 실행 계획도 깔끔하고 유지보수 포인트도 하나다.
카운트 쿼리도 같은 조건을 그대로 써야 한다는 게 포인트였다. 목록 쿼리에 필터 걸어놓고 total count는 필터 없이 잡으면 페이지네이션 숫자가 어긋난다. 실제로 이런 버그가 예전에 다른 목록에서 한 번 발생한 적 있었고, 그때 원인 추적하는 데 시간이 꽤 걸렸다. 그래서 이번엔 처음부터 count용 쿼리에도 동일한 <if> 블록을 복사해서 넣었다.
내부 클래스(DTO)에는 paymentMethod 필드를 추가하고, 뷰에서 넘어오는 파라미터 이름과 매핑되도록 맞췄다. JSP에서는 <select> 태그로 드롭다운 하나 추가하고, 기존 검색 폼의 AJAX 요청에 해당 값을 포함시켰다. 결제수단 코드는 DB에 문자열로 저장되어 있어서 선택지는 하드코딩으로 뒀다. 코드 테이블로 뺄 수도 있는데 지금 당장 변동 가능성이 없어서 오버엔지니어링 피하는 쪽 선택.
설계 때 실제로 고민한 것들
필터 추가가 단순해 보여도 결제 도메인이라 따져봐야 할 게 많았다.
| 항목 | 결정 | 이유 |
|---|---|---|
| 실시간 vs 폴링 | 폴링 없음, 조회 시점 스냅샷 | 목록은 관리자 확인 용도, 실시간성 불필요 |
| NULL 처리 | ''과 NULL 모두 방어 |
레거시 데이터 중 일부 NULL 존재 확인 |
| 권한 체크 | 기존 인터셉터 재사용 | 별도 권한 추가 없이 메뉴 접근 제어로 충분 |
| 클라이언트 필터 vs DB 필터 | DB 필터 | 페이지네이션 정합성 |
NULL 처리는 소홀하기 쉬운 부분인데, 레거시 데이터에 payment_method가 NULL로 들어간 게 있었다. paymentMethod != '' 조건만 체크했으면 NULL인 행이 모든 필터 케이스에서 포함됐을 거다. null 체크를 함께 달아줘야 의도한 대로 동작한다.
사이드바 메뉴 활성화도 빠뜨리기 쉬운 포인트. 기능 구현 다 해놓고 해당 메뉴가 active 상태가 안 되면 UX가 어색하다. 관련 JSP에서 현재 URL 기준으로 active 클래스를 잡는 방식이었는데, 새 경로 패턴을 추가해줬다.
금융 도메인에서 꼼꼼함이 기본값이어야 하는 이유
결제 관련 화면에서 숫자 하나가 어긋나면 운영팀에서 바로 인지한다. 다른 화면에서 보이는 합산값과 이 목록에서 보이는 건수가 맞지 않으면 "시스템 버그 아니냐"는 이슈가 생기고, 데이터 신뢰도가 흔들린다. 구현 후 검증할 때 단순히 "화면이 뜨는지" 확인하는 게 아니라 기존 화면 숫자와 cross-check를 했다. 필터 없이 전체 조회한 건수와 각 결제수단별 건수의 합이 일치하는지, 합산 금액이 기존 집계 화면과 맞는지 직접 확인했다.
"대충 맞는 것 같은데요"라고 넘어가면 얼마 뒤에 반드시 다시 돌아온다. 그때는 원인 파악 시간까지 더해지니까 처음에 확인하는 게 훨씬 싸다.
커밋은 기능 단위로 쪼갰다. DTO 변경, 매퍼 변경, 뷰 변경을 따로 커밋하니 나중에 git log 볼 때 어느 레이어에서 무엇이 바뀌었는지 바로 보임. 메시지도 "결제수단 필드 추가" 보다 "필터 조건 지원을 위해 paymentMethod 파라미터 추가"처럼 이유를 담으려고 했다. 사내 서비스 개발할수록 이 습관이 나중에 로그 볼 때 확실히 체감된다.
기능 자체는 작아도 SQL, DTO, 뷰, 권한, 메뉴 활성화까지 건드려야 하는 레이어가 이렇게 많다. 어느 하나 빠뜨리면 필터는 되는데 건수가 이상하거나, 기능은 동작하는데 메뉴가 어긋나 있거나 한다. 이게 사내 포탈 기능 추가의 현실이다.
댓글 0
첫 댓글 달아줘.