일기 slecs

정산 주문 매칭 로직을 변경 축 기준으로 분리해 누락 추적 속도를 높임

목차

파트너별 정산 화면에서 주문 누락이 보고됐다. 처음엔 단순 데이터 문제려니 했는데 파보니 로직 자체가 문제였다. 입금 데이터를 받아 주문과 매칭하고, 그 결과로 파트너를 결정한 뒤 계좌를 조회하는 흐름인데, 단계마다 책임이 뒤섞여 있어서 어느 단계에서 어긋난 건지 특정하는 데 시간이 너무 오래 걸렸다.

정산 시스템 특유의 복잡성이 있다. 입금 이벤트 하나가 여러 도메인 객체를 거치면서 "이 돈이 누구 것인가"를 판단하는 과정인데, 각 판단 단계가 명확히 분리되지 않으면 한 단계의 오류가 다음 단계로 조용히 넘어간다. 조용히 넘어가는 게 더 무섭다. 예외가 터지면 오히려 빠르게 잡히는데, 잘못된 값이 계속 흘러가면 정산 결과가 이상한 상태로 굳어버린다.

기존 컨트롤러가 한꺼번에 하고 있던 일:

  • 입금 식별자 파싱
  • 주문 후보 조회
  • 매칭 우선순위 판단
  • 매칭된 주문에서 파트너 추출
  • 파트너에 묶인 정산 계좌 조회
  • 응답 DTO 조립

한 메서드 안에 분기가 6단 7단까지 들어가니, 새 결제대행사가 붙을 때마다 if 가지가 또 늘어나는 구조였다. 이 상태에서 "어디서 틀렸는지" 찾으려면 디버그 로그를 켜고 처음부터 다 따라가야 했다.

분리 기준을 "변경 축"으로 잡은 이유

단순 메서드 분리와 책임 분리는 결이 다르다. 메서드를 쪼개는 건 코드 길이를 줄이는 거고, 책임을 쪼개는 건 "무엇이 바뀔 때 어디를 손대야 하는가"를 명확히 하는 거다. SRP를 "하나의 일만 한다"로 읽으면 오해가 생긴다. "하나의 변경 이유만 가진다"로 읽어야 설계가 달라진다.

이번 분리 기준:

책임 변경 트리거 분리 후 위치
주문 매칭 매칭 규칙/우선순위 매칭 도메인 서비스
파트너 결정 파트너 계층 변경 파트너 도메인 서비스
계좌 조회 정산 정책 계좌 조회 컴포넌트
HTTP 응답 API 스펙 컨트롤러

매칭 결과 → 파트너 → 계좌 순서 자체는 그대로 유지했다. 대신 각 단계가 자기 입력만 받고 자기 출력만 내도록 시그니처를 좁혔다.

입금ID → [매칭] → 주문
주문   → [파트너 결정] → 파트너
파트너 → [계좌 조회] → 정산계좌

이렇게 선형으로 만들어두면 테스트 경계도 자연히 생긴다. 매칭 서비스는 파트너를 모르고, 파트너 서비스는 계좌 정책을 모른다. 각자 자기 구간만 테스트하면 되고, 통합 시나리오는 파이프라인 전체를 보는 테스트 하나로 묶을 수 있다. 다음번에 매칭 규칙이 바뀌어도 건드릴 파일이 명확하다.

매칭 우선순위를 코드 구조로 드러낸 이유

기존엔 "금액 일치 + 시간대 근접"을 한 메서드 안에서 동시에 보고 있었다. 실제로는 판단 신뢰도에 순서가 있었는데, 그게 코드에 표현되지 않은 채 암묵적으로 동작하고 있었던 거다.

명시적으로 정한 순서:

  1. 가상계좌 식별자 일치 - 신뢰도 가장 높음, 오탐 거의 없음
  2. 입금자명 + 금액 일치
  3. 금액 + 시간 윈도우 매칭 - 동명이인, 같은 금액 충돌 가능성 있음

우선순위 자체가 새로운 규칙이 아니었다. 기존에도 암묵적으로 그렇게 동작하고 있었는데 코드 구조로 표현이 안 됐을 뿐이다. 이걸 명시적으로 만드는 순간 "왜 이게 매칭됐지?"라는 질문에 디버그 로그 없이도 답할 수 있게 된다. 케이스별 단위 테스트를 쪼개기도 훨씬 편해졌다. 3번 케이스 테스트를 짤 때 1번, 2번 조건이 없다는 전제를 명확히 설정할 수 있으니까.

비슷한 맥락에서, 우선순위 로직을 전략 패턴으로 빼두면 새 결제대행사가 붙을 때 기존 분기를 건드리지 않고 전략을 추가할 수 있다. 이번에 그 부분까지 완전히 전환하진 않았고, 구조만 그쪽으로 열어뒀다.

파트너 결정 가정이 깨진 케이스와 그 처리

"매칭된 주문 = 단일 파트너"라는 가정이 깨지는 케이스가 있었다. 환불/재발행으로 동일 입금이 두 파트너에 걸친 이력이 생기는 경우다. 이런 엣지 케이스는 초기 설계 때는 잘 안 보이다가 운영 데이터가 쌓이면서 드러난다.

이번 리팩터에서는 "최신 활성 주문 기준"으로 파트너를 결정하고, 다중 파트너 케이스는 경고 로그 + 운영 확인 큐로 빼뒀다. 자동 처리 로직을 지금 단계에서 넣으면 PR이 끝없이 커진다는 판단이 컸고, 도메인 규칙이 불명확한 상태에서 코드로 먼저 결정해버리면 나중에 되돌리기 어렵다. 운영팀이 직접 확인한 뒤 패턴이 보이면 그때 자동화 규칙을 추가하는 게 순서상 맞다.

이런 상황에서 자주 쓰는 패턴이 "보수적으로 처리하고 가시성부터 확보하는" 방식이다. 자동화보다 관찰을 먼저 하는 거다. 확인 큐에 쌓이는 케이스를 보면서 공통 패턴이 잡히면 그때 로직으로 굳히는 편이 훨씬 안전하다.

캐시를 의도적으로 빼둔 판단

파트너 → 계좌 매핑은 자주 안 바뀌니까 캐시 유혹이 있었다. 조회 빈도가 높으면 DB 부하도 신경 쓰이고. 그런데 정산 계좌가 잘못 캐시되면 돈이 엉뚱한 계좌로 갈 수 있다. 금융 도메인에서 캐시는 "빠르게 맞다"보다 "천천히 정확하다"를 골라야 하는 케이스가 많다. 특히 TTL이 길거나 무효화 로직이 불완전한 캐시는 조용한 사고의 원인이 된다.

이번엔 캐시 대신 호출 빈도 측정만 붙였다. 실제로 얼마나 자주 조회되는지 데이터를 먼저 보고, 그다음에 캐시가 필요한지 판단하려는 거다. "측정 없이 최적화하지 않는다"는 원칙인데, 정산처럼 정확성이 우선인 도메인에서는 더 엄격하게 지키는 편이다.

이번 작업에서 챙긴 것

메서드 수를 줄이는 것보다 "변경 축"을 기준으로 책임을 나누는 게 실질적인 유지보수 이득이 컸다. 나중에 합류한 팀원도 로그 켜지 않고 흐름을 읽을 수 있게 됐고, 디버깅 속도가 체감될 만큼 달라졌다.

PR 범위를 제어한 결정도 결과적으로 맞았다. 다중 파트너 케이스를 이번에 다 잡으려 했다면 리뷰 사이클이 두 배는 걸렸을 거다. 후속 작업으로 명확히 남겨두고 넘어간 게 팀 전체로 보면 더 빨랐다. 욕심 줄이는 게 머지 속도를 올린다는 걸 또 확인했다.

댓글 0

첫 댓글 달아줘.