개발 slecs

결제 파트너 포털에 신규 메뉴와 백엔드 API 추가

목차

partner-portal에 신규 메뉴와 백엔드 API를 붙이는 작업이었다. 언뜻 보면 메뉴 하나 추가하는 거라 작게 보이지만, 결제 도메인 특성상 화면 하나가 늘어날 때마다 쿼리 설계부터 권한 체계까지 다 따라 움직여야 한다.

변경 파일은 뷰/스타일 3개, 스크립트 1개. 반응형도 같이 손봤다. 사이드바 메뉴를 상시 노출로 바꾸고 모바일에서는 카드뷰 레이아웃으로 전환되게 했음.

기존 API가 없으면 쿼리부터 다시 짠다

기존 화면이나 API가 제공하지 않던 데이터가 필요한 경우, 그냥 프론트에서 있는 값 조합해서 계산하는 방법도 있긴 하다. 근데 결제 포털에서 그렇게 하면 안 된다. 두 화면의 숫자가 "원리상 같아야" 하는데 실제로 다르게 나오는 상황이 생기고, 그 불일치를 나중에 추적하는 비용이 훨씬 크다.

그래서 이번에도 쿼리 레벨부터 설계했다. 어느 테이블에서 어떤 기준으로 집계할지 먼저 정하고, 그게 기존 집계 로직과 동일한 기준인지 확인한 다음 API를 만들었음. 구현 순서로 정리하면 이렇다:

  1. 필요한 데이터 정의 - 화면에 뭘 보여줄지, 어떤 조건으로 필터할지
  2. 기존 쿼리/테이블 검토 - 새 테이블이 필요한지, 조인으로 해결되는지
  3. 백엔드 API 엔드포인트 구현
  4. 권한 인터셉터 통과 여부 확인
  5. JSP + AJAX로 프론트 연동
  6. 사이드바 메뉴 활성화 처리
  7. 관련 화면 숫자 cross-check

단계 7을 건너뛰는 경우가 종종 있는데, 이번엔 반드시 했다. 결제 데이터는 어느 화면에서 봐도 합계가 같아야 하니까.

설계 시 실제로 고민한 것들

설계 단계에서 가장 오래 본 건 세 가지였다.

폴링 vs 이벤트 기반 갱신. 파트너 포털 특성상 수치가 실시간으로 바뀌어야 하는 데이터인지, 새로고침 기준으로 보여줘도 되는 데이터인지를 먼저 구분했다. 실시간이 아니어도 되는 데이터에 폴링 붙이면 괜히 서버 부하만 늘어난다. 이번 기능은 요청 시점 스냅샷으로 충분하다고 판단해서 AJAX 단순 요청으로 처리했음.

예외 케이스 방어. 빈 데이터일 때, 권한이 없는 사용자가 직접 URL로 접근할 때, NULL이 섞인 집계 결과가 올 때. 이 세 가지를 API 레벨과 프론트 레벨 양쪽에서 각각 처리했다. 한쪽에서만 막으면 뭔가 뚫린다.

// NULL 집계 방어 - COALESCE로 DB에서 먼저 처리
SELECT
    partner_id,
    COALESCE(SUM(amount), 0)    AS total_amount,
    COALESCE(COUNT(txn_id), 0)  AS txn_count
FROM payment_txn
WHERE partner_id = :partnerId
  AND status != 'CANCELLED'
GROUP BY partner_id

프론트에서도 응답이 빈 배열이거나 null이면 "데이터 없음" 상태를 명시적으로 표시하도록 했다. 숫자 0과 데이터 없음은 다른 의미니까.

쿼리 실행 계획 사전 확인. 파트너별 집계 쿼리가 Full Scan으로 돌면 데이터가 쌓일수록 응답 시간이 선형으로 늘어난다. EXPLAIN 돌려보고 인덱스 타는 거 확인한 다음 배포했음. 이건 습관의 문제인데, 개발 환경 데이터가 적어서 로컬에선 빠르게 나와도 운영에서 터지는 케이스가 꼭 있다.

반응형 개선 - 메뉴 상시 노출과 모바일 카드뷰

이번에 UI 쪽도 같이 손봤다. 기존에는 사이드바 메뉴가 특정 조건에서 숨겨지는 구조였는데, 파트너 포털 특성상 메뉴가 항상 보이는 게 더 낫다는 피드백이 있었음. 그래서 상시 노출로 바꿨다.

모바일 쪽은 테이블 레이아웃을 카드뷰로 전환하는 방식으로 처리했다. 결제 내역처럼 컬럼이 많은 데이터는 모바일에서 테이블로 보면 가로 스크롤이 너무 길어지기 때문에, breakpoint 아래에서는 각 행을 카드 형태로 묶어서 보여주는 게 훨씬 읽기 편하다.

/* 모바일 카드뷰 전환 */
@media (max-width: 768px) {
  .data-table thead { display: none; }
  .data-table tr {
    display: block;
    margin-bottom: 12px;
    border: 1px solid #e0e0e0;
    border-radius: 6px;
    padding: 12px;
  }
  .data-table td {
    display: flex;
    justify-content: space-between;
    padding: 4px 0;
  }
  .data-table td::before {
    content: attr(data-label);
    font-weight: 600;
    color: #555;
  }
}

data-label 속성을 JSP 템플릿에서 각 <td>에 박아두면, CSS만으로 컬럼명을 레이블로 재활용할 수 있다. JS 없이 반응형 테이블을 카드로 바꾸는 가장 간단한 방법 중 하나임.


사내 서비스를 계속 만들다 보면 "기능 하나 추가"가 얼마나 많은 레이어를 건드리는지 매번 새삼 느낀다. SQL 집계, 상태 처리, 예외 방어, 화면 렌더링, 권한 체크가 전부 맞물려 있어서 어느 하나만 빠뜨려도 특정 사용자한테 이상한 화면이 나오거나 숫자가 안 맞는다.

특히 금융/결제 쪽은 숫자 하나가 틀리면 신뢰가 흔들릴 수 있어서, "대충 맞는 것 같다"는 판단으로 넘어가면 반드시 나중에 다시 돌아온다. 꼼꼼함이 기본값이어야 하는 도메인이다.

작업 전후 수치를 메모해두고, 관련 화면 cross-check을 루틴화하는 것. 그리고 커밋은 "무엇을"보다 "왜"를 담으려는 것. 이 두 가지 습관이 이번 작업에서도 유효했다. 논리적으로 독립된 단위로 커밋을 쪼개두면 나중에 뭔가 깨졌을 때 원인 찾는 시간이 확 줄어든다.

댓글 0

첫 댓글 달아줘.