개발 slecs

결제 도메인 신규 기능을 쿼리부터 화면까지 정합성 있게 구현

목차

결제 도메인에 기능을 하나 추가하는 작업이었는데, 끝내고 나니 변경 파일이 뷰/스타일 포함 6개였다. 숫자만 보면 작아 보이지만 실제로는 SQL 매퍼부터 내부 클래스 메서드, 화면 렌더링까지 레이어를 전부 건드린 작업이었음.

발단은 기존 화면과 API에서 제공하지 않던 데이터나 동작이 필요해진 것. 이런 요구사항이 들어오면 두 가지 접근이 있다. 빠르게 화면에 붙이는 것, 아니면 데이터 흐름을 처음부터 다시 설계하는 것. 금융/결제 도메인에서 전자를 선택하면 거의 예외 없이 나중에 다시 돌아온다. 그래서 이번엔 쿼리 레벨부터 설계해서 정합성을 맞추는 방향으로 갔음.

레이어별로 어떻게 붙였나

구현 순서는 쿼리 → 내부 클래스 메서드 → 화면 렌더링 순이었다.

SQL 매퍼에 쿼리를 먼저 작성한 이유는, 화면에서 "필요한 것 같은" 데이터를 나중에 쿼리로 억지로 끌어오려 하면 집계 로직이 어긋나거나 N+1 문제가 생기기 때문. 반대로 쿼리가 줄 수 있는 데이터 범위를 먼저 확정하면 화면 설계가 그 안에서 맞춰진다.

쿼리 작성할 때 챙긴 것들:

  • 실행 계획 사전 확인 - 결제 관련 테이블은 대부분 데이터가 쌓여 있어서 풀스캔이 나면 바로 체감됨
  • 기존 집계 쿼리와 값 기준이 일치하는지 - 화면마다 "합계"의 정의가 다르면 숫자가 달라 보임
  • NULL 케이스 방어 - COALESCEIFNULL 없이 합산하면 한 행만 NULL이어도 전체 결과가 날아감
-- 예: NULL 방어 + 기존 집계와 기준 통일
SELECT
  user_id,
  COALESCE(SUM(amount), 0)      AS total_amount,
  COALESCE(SUM(fee_amount), 0)  AS total_fee,
  COUNT(*)                       AS tx_count
FROM payment_transactions
WHERE status IN ('COMPLETED', 'PARTIAL')
  AND created_at BETWEEN :from AND :to
GROUP BY user_id

쿼리가 확정되면 내부 클래스에 메서드를 추가했다. 기존 로직과의 충돌 여부를 체크하는 게 이 단계에서 제일 신경 쓰이는 부분인데, 결제 도메인은 상태 머신이 얽혀 있어서 새 메서드가 기존 트랜잭션 흐름과 겹치면 이상한 타이밍에 문제가 터진다. 이번엔 읽기 전용 조회 기능이라 상태 변경과의 충돌은 없었지만, 그래도 기존 코드 경로를 한 번 쭉 따라가면서 확인했음.

화면 렌더링 연동은 마지막. 모바일 해상도 대응 CSS도 이 시점에 같이 붙였는데, responsive-ui 영역에 추가된 거라 기존 레이아웃에 영향 주지 않게 미디어 쿼리 범위를 좁게 잡았다.

설계할 때 실제로 고민한 것들

실시간 갱신이 필요한가. 결제 현황 같은 데이터는 "거의 실시간"과 "완전 실시간"의 구현 비용이 크게 다르다. 폴링이면 인터벌만 조정하면 되고, 이벤트 기반이면 메시지 큐나 웹소켓 연동이 들어간다. 이번 기능은 관리자가 수동으로 조회하는 케이스가 대부분이라 폴링 없이 페이지 로드 시 조회로 결론냈음. 무조건 실시간이 좋은 게 아니라, 사용 패턴에 맞는 방식을 선택하는 게 낫다.

권한 없는 접근. 결제 데이터는 조회 권한 체크가 기본. 쿼리에서 사용자 범위를 필터링하는 것 외에, 화면 렌더링 레이어에서도 권한 없는 사용자에게는 데이터 자체를 내려주지 않도록 했다. 프론트에서만 막으면 API 직접 호출로 뚫릴 수 있음.

예외 케이스. 빈 데이터 상태, NULL, 기간 내 거래가 없는 사용자 - 이것들을 전부 화면에서 자연스럽게 처리해야 한다. 집계 결과가 없을 때 0을 보여줄지, 아예 행을 숨길지, "-"를 보여줄지는 도메인 컨텍스트에 따라 다르다. 이번엔 0과 "-"를 구분해야 하는 케이스가 있어서 쿼리 결과에 그 구분자를 포함시켰다.

상황 처리 방식
거래 자체가 없음 행 미출력
거래 있으나 금액 NULL 0으로 대체, 별도 표기
권한 없는 데이터 API 레벨 차단, 화면 미노출
조회 기간 외 데이터 쿼리 범위 제한으로 원천 차단

검증과 습관

구현 후 직접 화면에서 동작을 확인했고, 기존 데이터가 깨지지 않았는지 관련 화면의 숫자를 cross-check했다. 결제 도메인에서 이 과정을 생략하면 "숫자가 맞는 것 같다"는 느낌으로 넘어가게 되는데, 나중에 반드시 다시 돌아옴.

변경 전 수치를 메모해두고, 수정 후 같은 케이스로 비교하는 습관은 오래됐다. 스크린샷이든 텍스트 메모든 형식은 상관없는데, 비교 기준이 있어야 "달라졌다"를 인식할 수 있다.

커밋은 논리적으로 독립된 단위로 쪼개는 걸 유지 중. 쿼리 추가, 메서드 추가, 화면 연동을 한 커밋에 묶으면 나중에 어떤 변경에서 문제가 생겼는지 찾기가 훨씬 어려워진다. 커밋 메시지는 "무엇을"보다 "왜"를 담으려고 노력하는데, 3개월 후에 git log를 봤을 때 그 커밋이 왜 필요했는지 맥락이 남아 있어야 디버깅이 수월하다.

사내 서비스를 계속 만들다 보면 기능 하나가 화면 버튼 하나로 끝나는 경우는 거의 없다는 걸 반복해서 체감한다. SQL 집계, 상태 머신, 예외 처리, 화면 렌더링, 권한 체크가 전부 엮여 있어서 어느 하나를 빠뜨리면 숫자가 안 맞거나 특정 사용자에게만 이상한 화면이 나온다. 금융/결제 쪽은 특히 숫자 하나가 틀리면 신뢰가 무너질 수 있어서, 꼼꼼함이 선택이 아니라 기본값이어야 한다.

댓글 0

첫 댓글 달아줘.