개발 slecs

입금자명 파싱 다단계 분류와 정산 계좌 조회 엔드포인트 분리

목차

문자 메시지에서 입금자명을 뽑는 건 처음엔 별 게 아닌 것처럼 보인다. 어차피 "입금 홍길동 100,000원" 같은 포맷 아닌가 싶었는데, 실제 운영 데이터를 까보면 그 생각이 30초도 안 가서 깨진다.

입금자명 파싱 - 분류 먼저, 추출은 그다음

기존 로직은 단일 정규식이었다. 나름 잘 만들었다고 생각했는데, 매칭 실패율이 묵인하기 어려운 수준으로 쌓여 있었다. 원인을 하나씩 추적해보니 케이스가 생각보다 훨씬 다양했다.

  • 은행마다 메시지 포맷이 다름 - 콜론 위치, 줄바꿈 위치, 특수문자 종류까지 제각각
  • 영문/한글 이름 혼합 케이스 (Kim Gilsoon, 김Gil순 같은 것도 실제로 옴)
  • 법인명 뒤에 담당자명이 붙는 패턴 ((주)○○ 홍길동 - 어디까지가 이름인지 판단이 필요)
  • 광고 문구가 본문 중간에 끼어드는 메시지 (일부 은행은 잔액 안내 뒤에 프로모션 문구가 붙음)

단일 패턴으로 이걸 다 잡으려다 보니 패턴이 점점 길어지고, 길어질수록 특정 케이스를 잡으면 다른 케이스가 깨지는 악순환이었다. 결국 방향을 바꿨다. 추출 전에 분류를 먼저 하는 것.

INPUT  : "[Web발신] ○○은행 입금 100,000 홍길동 잔액..."

STEP 1 : 메시지 타입 판별 (입금 / 출금 / 광고 / 알 수 없음)
STEP 2 : 발신처별 토큰 위치 매핑
          - A은행: 금액 토큰 다음 첫 번째 한글 토큰
          - B은행: "입금인" 키워드 이후 콜론 오른쪽
          - 기본값: 아래 폴백 규칙 적용
STEP 3 : 이름 후보 정규화 (공백/특수문자 제거, 법인 접두어 처리)

OUTPUT : "홍길동"

각 발신처별 토큰 위치는 따로 매핑 테이블로 관리한다. 이 테이블만 건드리면 새 은행 포맷 추가가 가능한 구조. 분류 자체가 실패하거나 매핑이 없는 발신처면 폴백으로 떨어진다. 폴백은 간단하다 - 숫자 토큰, 금액 단위 토큰, 특수문자 위주 토큰을 전부 제외하고 남은 한글 토큰 중 가장 짧은 걸 이름 후보로 잡는다. 완벽하진 않지만, 아무것도 못 잡는 것보단 낫다.

이 구조에서 중요한 건 분류 실패를 명시적으로 처리한다는 거다. 기존엔 단일 패턴이 실패하면 빈 문자열이 조용히 넘어갔다. 이제는 타입이 UNKNOWN이면 폴백 결과와 함께 신뢰도 점수도 같이 내려서, 호출 측에서 수동 확인 큐로 넘길 수 있게 했다.

파싱 로직 작업에서 배운 한 가지는, "한 정규식으로 끝낸다"는 욕심 자체를 빨리 버려야 한다는 거다. 입력의 다양성을 인정하고 분류 단계를 두면 각 규칙이 단순해진다. 규칙이 단순하면 디버깅도 쉽고, 새 케이스 추가도 쉽다. 복잡한 정규식 하나보다 단순한 규칙 여러 개가 관리 면에서 훨씬 낫다.

정산 계좌 조회 엔드포인트 분리

파트너가 등록한 정산 계좌 정보를 다른 모듈에서 조회하는 케이스가 계속 늘었다. 기존엔 파트너 상세 응답에 계좌 정보가 묶여서 내려갔는데, 계좌 필드만 필요한 내부 모듈도 파트너 풀 페이로드를 받아야 하는 상황이었다. 응답 크기도 문제지만, 호출 권한 구조도 어색했다. 관리자 전용 엔드포인트를 내부 모듈이 그대로 타고 있었으니까.

항목 변경 전 변경 후
응답 크기 파트너 풀 정보 전체 계좌 번호, 은행 코드, 예금주만
호출 권한 관리자 세션 토큰 내부 서비스 키
캐시 없음 짧은 TTL (계좌 변경 빈도 낮음)
엔드포인트 GET /partners/:id GET /partners/:id/bank-account

권한 체크를 분리한 부분이 이번 작업에서 제일 신경 쓴 지점이다. 내부 호출 경로와 사용자 호출 경로가 섞이기 시작하면 나중에 권한 사고 나기 딱 좋다. 지금은 작아 보여도, 모듈 수가 늘거나 외부 파트너 API로 확장되는 순간 뒤엉킨 권한 체계가 터진다. 같은 계좌 정보를 어떤 경로로 누가 조회했는지 로그로 구분하기도 어려워지고.

캐시 TTL은 짧게 잡았다. 계좌 정보는 자주 바뀌지 않지만, 바뀌었을 때 즉각 반영이 안 되면 정산 오류로 이어질 수 있어서 너무 길게 두기가 부담스러웠다. 지금은 보수적으로 잡아두고, 실제 변경 빈도 데이터 보면서 조정할 생각.

API 응답을 처음부터 좁게 짜는 습관이 중요하다는 걸 이번에 다시 느꼈다. 처음엔 풀 응답 하나로 다 커버하는 게 편하다. 근데 호출자가 두셋 생기는 순간부터 각자 다른 필드만 쓰는데 같은 큰 페이로드를 받는 구조가 된다. 그게 쌓이면 불필요한 직렬화, 불필요한 DB 조회, 불필요한 네트워크 비용이다. 나중에 분리하려면 기존 호출자 전부 영향 분석해야 하고, 이미 의존하는 곳이 많아서 손대기 겁나는 상태가 된다. 처음부터 목적에 맞게 좁게 내리는 게 맞다.

사이드 작업 - 푸터 정렬, 스토어 스타일 보정

메인 작업하다 눈에 들어온 푸터 정렬 깨짐과 스토어 페이지 스타일 이슈도 같이 처리했다. 원래는 별 커밋으로 분리하고 싶었는데, 같은 페이지 안에서 발견한 것들이라 한 PR로 묶는 게 리뷰어 입장에서 오히려 편하다고 판단했다. 분리해야 할 만큼 크지도 않았다. 파일 두 개, 변경 줄 얼마 안 됨.

UI 자잘한 깨짐은 발견 즉시 같이 처리하는 게 맞다. 별 티켓으로 빼면 백로그에 묻히고, 백로그에서 꺼내지면 그때는 컨텍스트가 없어서 다시 뜯어봐야 한다. 이미 그 화면 보고 있는 김에 3분 써서 고치는 게 나중에 30분 쓰는 것보다 낫다.


오늘 작업 전반을 돌아보면, 공통된 흐름이 하나 있다. 처음엔 단순하게 접근했다가 실제 데이터/실제 호출 패턴을 보고 나서 구조를 바꾼 것들이다. 파싱도, 엔드포인트 분리도 전부 그랬다. "이 정도면 충분하겠지"로 시작해서 실측치를 보고 설계를 고치는 사이클. 이게 귀찮긴 한데, 초기 가정이 틀린 걸 빨리 인정하고 고치는 편이 나중에 더 크게 고치는 것보다는 낫다.

댓글 0

첫 댓글 달아줘.