개발 slecs

결제 대시보드에 정합성 검증된 신규 기능 추가

목차

system-revenue 영역에 새 기능을 붙이는 작업을 했다. 표면상으로는 대시보드에 어르신 친화 UX를 개선하는 변경이고, 실제 diff는 뷰/스타일 파일 1개. 근데 그 파일 하나에 도달하기까지의 과정이 단순하지 않았다.

파일 하나가 변경됐다고 해서 작업이 가벼웠다는 뜻은 아니다. 뷰에서 데이터를 받아 그리는 건 마지막 단계고, 그 앞에 쿼리, 서비스 레이어, 권한 처리가 다 얹혀 있다. 결제 대시보드 특성상 숫자 하나가 틀리면 신뢰 문제로 직결되기 때문에, 새 기능이라도 기존 데이터와 정합성이 맞는지 확인하는 게 기본값이다.

한 화면 뒤에 있는 것들

기존 화면과 API에서 제공하지 않던 데이터가 필요해졌다. 이런 상황에서 가장 빠른 방법은 프론트에서 기존 응답 값을 조합해 계산하거나, 임시로 API를 하나 끼워넣는 것이다. 그 방식은 처음엔 빠르지만 결국 리팩터링으로 돌아온다. 임시 계산 로직이 흩어져 있으면 어느 화면 숫자가 기준인지 파악하기 어려워지고, 버그가 생겼을 때 원인을 찾는 시간이 곱절로 든다.

그래서 이번엔 쿼리 레벨부터 짚었다. 흐름은 단순하다.

  1. 내부 클래스에 메서드 추가
  2. SQL 매퍼에 쿼리 작성
  3. 화면 렌더링 연동
  4. 기존 로직과 충돌 여부 확인

각 레이어의 책임을 명확히 가져가는 게 목표였음. 서비스 레이어가 집계 로직을 들고 있고, SQL 매퍼는 데이터 조회만 한다. 뷰는 받은 데이터를 그리는 것만 신경 쓴다. 이 경계가 흐려지면 나중에 수정할 때 어디를 건드려야 하는지 헷갈린다.

SQL 매퍼 단계에서 실행 계획을 미리 확인하는 습관이 생겼다. 대시보드성 쿼리는 집계 연산이 들어가는 경우가 많고, 조회 범위가 넓으면 풀 스캔이 뜰 수 있다.

-- 실행 계획 확인 예시
EXPLAIN SELECT
  user_id,
  SUM(amount) AS total_revenue,
  COUNT(*)    AS tx_count
FROM transactions
WHERE status     = 'SETTLED'
  AND created_at BETWEEN :from AND :to
GROUP BY user_id;

created_at + status 복합 인덱스가 있는지 먼저 확인해두면 나중에 놀랄 일이 줄어든다. 새로 추가한 쿼리가 기존 쿼리보다 현저히 느리면 대시보드 전체 로딩이 느려지는 것이기 때문에, 설계 단계에서 실행 계획을 보는 게 낫다. "느리면 나중에 최적화하자"는 금융 도메인에서는 통하지 않는 말이다.

정합성을 맞추는 게 생각보다 일이다

결제 도메인에서 '화면에 숫자 보여주기'는 단순한 READ처럼 보인다. 근데 그 숫자가 다른 화면에서 보이는 값과 일치해야 한다는 조건이 붙으면 이야기가 달라진다. 동일한 도메인 데이터를 두 경로로 조회하면 결과가 달라지는 경우가 생각보다 자주 있다. 집계 기준 시간대, NULL 처리, 취소된 거래 포함 여부 같은 게 엣지로 숨어있다.

이번 작업에서 방어한 케이스:

케이스 처리 방식
데이터 없음 (empty result) 0 또는 '-' 렌더링, NPE 방지
NULL 값 COALESCE로 기본값 보정
권한 없는 접근 쿼리 진입 전 권한 체크
기존 집계와 합산 기준 불일치 동일 필터 조건 맞춤

NULL 처리는 한 군데만 빠져도 화면이 비거나 합산이 틀려진다. COALESCE(amount, 0) 같은 보정을 쿼리 단에서 미리 해두면 애플리케이션 레이어가 훨씬 단순해진다. 반대로 뷰에서 null 체크를 매번 하는 방식은 동일한 방어 코드가 여러 곳에 흩어지는 형태가 된다.

실시간 갱신이 필요한지도 따졌다. 폴링이냐 이벤트 드리븐이냐인데, 이 화면은 페이지 진입 시 1회 조회로 충분하다고 봤다. 이벤트 드리븐으로 가면 정확하지만 구조가 복잡해지고, 이 기능에서 밀리초 단위 실시간성이 필요한 상황이 아니었으니 오버엔지니어링이 된다. "실시간으로 갱신되어야 한다"는 요건이 명확하게 있을 때만 그 비용을 지불하는 게 맞다.

어르신 친화 UX라는 요건도 있었는데, 여기서는 정보 밀도보다 가독성을 우선했음. 숫자 크기, 간격, 레이블 명확성 같은 부분을 스타일에서 챙겼다. 기능적으로 데이터가 맞더라도 사용자가 읽기 불편하면 의미가 없으니까.

구현하고 나서

구현 후 직접 화면에서 동작을 확인했다. 기존에 다른 화면에서 보이던 숫자와 이번에 추가한 집계 수치가 일치하는지 cross-check했고, 기존 기능이 그대로인지도 같이 확인했다. 변경 전 현재 동작을 수치로 메모해두고 수정 후 같은 케이스로 확인하는 게 루틴이 됐는데, 기억에 의존하면 "원래 이 숫자가 맞았나?"를 다시 추적해야 하는 일이 생긴다. 메모 하나가 그 시간을 아껴준다.

사내 서비스를 만들다 보면 기능 하나가 단순히 화면에 버튼 하나 추가하는 것으로 끝나지 않는다는 걸 계속 체감함. SQL 집계, 상태 처리, 예외 방어, 렌더링, 권한 체크가 모두 엮여있어서 어느 하나만 빠뜨려도 특정 사용자에게 이상한 화면이 나오거나 숫자가 맞지 않는다. '대충 맞는 것 같다'로 넘기면 반드시 다시 돌아온다. 금융/결제 도메인에서는 꼼꼼함이 기본값이어야 한다.

커밋은 논리적으로 독립된 단위로 쪼개는 편이다. 매퍼 쿼리 추가, 서비스 메서드 추가, 뷰 연동을 하나의 커밋으로 묶으면 나중에 문제가 생겼을 때 어느 변경에서 깨졌는지 찾기가 힘들다. 작은 커밋이 번거롭게 느껴질 수 있지만 git bisectgit log로 범위를 좁힐 때 확실히 낫다는 걸 경험으로 안다.

커밋 메시지는 "무엇을"보다 "왜"를 담으려고 노력한다. 뷰 수정보다 어르신 친화 UX 개선을 위한 revenue 집계 노출이 나중에 맥락을 파악할 때 훨씬 도움이 됨. 몇 달 뒤의 나 혹은 다른 팀원이 읽는다고 생각하면 메시지에 더 공들이게 된다. 코드는 실행되는 문서고, 커밋 히스토리는 의사결정 로그다.

댓글 0

첫 댓글 달아줘.